diff --git a/.chronus/changes/python-spector-tests-arm-optemplate-clientapiversions-2026-7-27-8-55-0.md b/.chronus/changes/python-spector-tests-arm-optemplate-clientapiversions-2026-7-27-8-55-0.md new file mode 100644 index 00000000000..670327c9aad --- /dev/null +++ b/.chronus/changes/python-spector-tests-arm-optemplate-clientapiversions-2026-7-27-8-55-0.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@typespec/http-client-python" +--- + +Add mock API tests for new Spector scenarios: ARM operation templates (Legacy RoutedOperations, LRO on GET, POST LRO with paging body, POST action paging, markAsPageable) and `@clientApiVersions` decorator. diff --git a/packages/http-client-python/package-lock.json b/packages/http-client-python/package-lock.json index 24fce49d063..ff328ef2aad 100644 --- a/packages/http-client-python/package-lock.json +++ b/packages/http-client-python/package-lock.json @@ -1,12 +1,12 @@ { "name": "@typespec/http-client-python", - "version": "0.34.2", + "version": "0.35.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@typespec/http-client-python", - "version": "0.34.2", + "version": "0.35.0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -17,7 +17,7 @@ "tsx": "^4.21.0" }, "devDependencies": { - "@azure-tools/azure-http-specs": "0.1.0-alpha.43", + "@azure-tools/azure-http-specs": "0.1.0-alpha.44-dev.5", "@azure-tools/typespec-autorest": "~0.70.0", "@azure-tools/typespec-azure-core": "~0.70.0", "@azure-tools/typespec-azure-resource-manager": "~0.70.0", @@ -66,25 +66,141 @@ } }, "node_modules/@azure-tools/azure-http-specs": { - "version": "0.1.0-alpha.43", - "resolved": "https://registry.npmjs.org/@azure-tools/azure-http-specs/-/azure-http-specs-0.1.0-alpha.43.tgz", - "integrity": "sha512-5FTIh21iGlMtVkQHZrbFPg7fIDYtIxjG1oYNbcxdirfQUm/XtWvCv8UAwr3OzEBrE1+YEC6c164x4+bINm5UHA==", + "version": "0.1.0-alpha.44-dev.5", + "resolved": "https://registry.npmjs.org/@azure-tools/azure-http-specs/-/azure-http-specs-0.1.0-alpha.44-dev.5.tgz", + "integrity": "sha512-YLDaHQMVyH1A/0UMHHREG3729QV+hgArQKWMK4hPoBbCImRAuk+Agaog3RLRPKM98yFbNVqyxkrWF0JdT8+M9g==", "dev": true, "license": "MIT", "dependencies": { - "@typespec/spec-api": "^0.1.0-alpha.15", - "@typespec/spector": "^0.1.0-alpha.26" + "@typespec/spec-api": "^0.1.0-alpha.15 || >=0.1.0-alpha.16-dev <0.1.0-alpha.16", + "@typespec/spector": "^0.1.0-alpha.27 || >=0.1.0-alpha.28-dev <0.1.0-alpha.28" }, "engines": { "node": ">=22.0.0" }, "peerDependencies": { - "@azure-tools/typespec-azure-core": "^0.70.0", + "@azure-tools/typespec-azure-core": "^0.70.0 || >=0.71.0-dev <0.71.0", + "@typespec/compiler": "^1.14.0", + "@typespec/http": "^1.14.0", + "@typespec/rest": "^0.84.0 || >=0.85.0-dev <0.85.0", + "@typespec/versioning": "^0.84.0 || >=0.85.0-dev <0.85.0", + "@typespec/xml": "^0.84.0 || >=0.85.0-dev <0.85.0" + } + }, + "node_modules/@azure-tools/azure-http-specs/node_modules/@typespec/spector": { + "version": "0.1.0-alpha.27", + "resolved": "https://registry.npmjs.org/@typespec/spector/-/spector-0.1.0-alpha.27.tgz", + "integrity": "sha512-Um9DcYa/emNAN1RjuBCgGawSXfaACp+b16D4YDnCr+jsSnclAcRwbKl3hwPkRjFiWSUy2Qvdots16MCLAD9w7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.13.1", "@typespec/compiler": "^1.14.0", "@typespec/http": "^1.14.0", "@typespec/rest": "^0.84.0", + "@typespec/spec-api": "^0.1.0-alpha.15", + "@typespec/spec-coverage-sdk": "^0.1.0-alpha.16", "@typespec/versioning": "^0.84.0", - "@typespec/xml": "^0.84.0" + "ajv": "^8.18.0", + "express": "^5.2.1", + "micromatch": "^4.0.8", + "morgan": "^1.10.1", + "multer": "^2.2.0", + "picocolors": "^1.1.1", + "source-map-support": "^0.5.21", + "yaml": "^2.8.3", + "yargs": "^18.0.0" + }, + "bin": { + "tsp-spector": "cmd/cli.mjs" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure-tools/azure-http-specs/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@azure-tools/azure-http-specs/node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@azure-tools/azure-http-specs/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@azure-tools/azure-http-specs/node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@azure-tools/azure-http-specs/node_modules/yargs": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/@azure-tools/azure-http-specs/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/@azure-tools/typespec-autorest": { @@ -2929,10 +3045,11 @@ } }, "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -4267,9 +4384,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "dev": true, "license": "MIT", "engines": { @@ -6413,12 +6530,13 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" diff --git a/packages/http-client-python/package.json b/packages/http-client-python/package.json index 25f1b0409a5..32d28115fac 100644 --- a/packages/http-client-python/package.json +++ b/packages/http-client-python/package.json @@ -108,7 +108,7 @@ "@azure-tools/typespec-azure-resource-manager": "~0.70.0", "@azure-tools/typespec-azure-rulesets": "~0.70.0", "@azure-tools/typespec-client-generator-core": "~0.70.0", - "@azure-tools/azure-http-specs": "0.1.0-alpha.43", + "@azure-tools/azure-http-specs": "0.1.0-alpha.44-dev.5", "@typespec/compiler": "^1.14.0", "@typespec/http": "^1.14.0", "@typespec/openapi": "^1.14.0", diff --git a/packages/http-client-python/tests/mock_api/azure/asynctests/test_azure_arm_operationtemplates_async.py b/packages/http-client-python/tests/mock_api/azure/asynctests/test_azure_arm_operationtemplates_async.py index d28064a1bcc..01192f03a15 100644 --- a/packages/http-client-python/tests/mock_api/azure/asynctests/test_azure_arm_operationtemplates_async.py +++ b/packages/http-client-python/tests/mock_api/azure/asynctests/test_azure_arm_operationtemplates_async.py @@ -204,3 +204,105 @@ async def test_lro_paging_begin_post_paging_lro(client): assert items[1].name == "product2" assert items[1].properties.product_id == "product2" assert items[1].properties.provisioning_state == "Succeeded" + + +@pytest.mark.asyncio +async def test_lro_begin_get_lro(client): + result = await ( + await client.lro.begin_get_lro( + scope=f"/subscriptions/{SUBSCRIPTION_ID}/resourceGroups/{RESOURCE_GROUP_NAME}", + operation_id="report1", + ) + ).result() + assert result.name == "report1" + assert result.properties.download_url == "https://storage.blob.core.windows.net/reports/report1.csv" + assert result.properties.provisioning_state == "Succeeded" + + +@pytest.mark.asyncio +async def test_lro_paging_begin_post_paging_lro_with_body(client): + poller = await client.lro_paging.begin_post_paging_lro_with_body( + resource_group_name=RESOURCE_GROUP_NAME, + product_name="default", + body=models.VnetProfile(vnet_id="vnet1"), + ) + result = await poller.result() + items = [item async for item in result] + assert len(items) == 2 + assert items[0].name == "product1" + assert items[0].properties.product_id == "product1" + assert items[0].properties.provisioning_state == "Succeeded" + assert items[1].name == "product2" + assert items[1].properties.product_id == "product2" + assert items[1].properties.provisioning_state == "Succeeded" + + +@pytest.mark.asyncio +async def test_legacy_routed_get(client): + result = await client.legacy.routed_get( + resource_group_name=RESOURCE_GROUP_NAME, + name="default", + diagnostic_name="memory", + ) + assert result.name == "memory" + assert result.status == "healthy" + + +@pytest.mark.asyncio +async def test_legacy_create_or_replace_optional_body_without_body(client): + result = await client.legacy.create_or_replace_optional_body( + resource_group_name=RESOURCE_GROUP_NAME, + configuration_name="default", + ) + assert result.name == "default" + assert result.properties.config_value == "default-value" + assert result.properties.provisioning_state == "Succeeded" + + +@pytest.mark.asyncio +async def test_legacy_create_or_replace_optional_body_with_body(client): + result = await client.legacy.create_or_replace_optional_body( + resource_group_name=RESOURCE_GROUP_NAME, + configuration_name="default", + resource=models.Configuration( + location="eastus", + properties=models.ConfigurationProperties(config_value="custom-value"), + ), + ) + assert result.name == "default" + assert result.properties.config_value == "custom-value" + assert result.properties.provisioning_state == "Succeeded" + + +@pytest.mark.asyncio +async def test_paging_post_action_paging(client): + items = [ + item + async for item in client.paging.post_action_paging( + resource_group_name=RESOURCE_GROUP_NAME, + monitor_name="monitor1", + body=models.LogStatusRequest(filter="status eq 'active'"), + ) + ] + assert len(items) == 1 + assert ( + items[0].id + == f"/subscriptions/{SUBSCRIPTION_ID}/resourceGroups/{RESOURCE_GROUP_NAME}/providers/Microsoft.Compute/virtualMachines/vm1" + ) + assert items[0].sending_metrics is True + + +@pytest.mark.asyncio +async def test_paging_mark_as_pageable(client): + items = [ + item + async for item in client.paging.mark_as_pageable( + resource_group_name=RESOURCE_GROUP_NAME, + monitor_name="monitor1", + ) + ] + assert len(items) == 2 + assert items[0].name == "collection1" + assert items[0].properties.display_name == "Test Collection" + assert items[1].name == "collection2" + assert items[1].properties.display_name == "Another Collection" diff --git a/packages/http-client-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_client_api_versions_async.py b/packages/http-client-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_client_api_versions_async.py new file mode 100644 index 00000000000..64a7f61afb9 --- /dev/null +++ b/packages/http-client-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_client_api_versions_async.py @@ -0,0 +1,19 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +import pytest_asyncio +from azure.clientgenerator.core.apiversion.clientapiversions.aio import ClientApiVersionsClient + + +@pytest_asyncio.fixture +async def client(): + async with ClientApiVersionsClient(endpoint="http://localhost:3000", api_version="2022-10-01") as client: + yield client + + +@pytest.mark.asyncio +async def test_send_api_version(client: ClientApiVersionsClient): + await client.send_api_version() diff --git a/packages/http-client-python/tests/mock_api/azure/test_azure_arm_operationtemplates.py b/packages/http-client-python/tests/mock_api/azure/test_azure_arm_operationtemplates.py index c86c95ef4a9..790ad75c3ed 100644 --- a/packages/http-client-python/tests/mock_api/azure/test_azure_arm_operationtemplates.py +++ b/packages/http-client-python/tests/mock_api/azure/test_azure_arm_operationtemplates.py @@ -178,3 +178,93 @@ def test_lro_paging_begin_post_paging_lro(client): assert items[1].name == "product2" assert items[1].properties.product_id == "product2" assert items[1].properties.provisioning_state == "Succeeded" + + +def test_lro_begin_get_lro(client): + result = client.lro.begin_get_lro( + scope=f"/subscriptions/{SUBSCRIPTION_ID}/resourceGroups/{RESOURCE_GROUP_NAME}", + operation_id="report1", + ).result() + assert result.name == "report1" + assert result.properties.download_url == "https://storage.blob.core.windows.net/reports/report1.csv" + assert result.properties.provisioning_state == "Succeeded" + + +def test_lro_paging_begin_post_paging_lro_with_body(client): + result = client.lro_paging.begin_post_paging_lro_with_body( + resource_group_name=RESOURCE_GROUP_NAME, + product_name="default", + body=models.VnetProfile(vnet_id="vnet1"), + ).result() + items = list(result) + assert len(items) == 2 + assert items[0].name == "product1" + assert items[0].properties.product_id == "product1" + assert items[0].properties.provisioning_state == "Succeeded" + assert items[1].name == "product2" + assert items[1].properties.product_id == "product2" + assert items[1].properties.provisioning_state == "Succeeded" + + +def test_legacy_routed_get(client): + result = client.legacy.routed_get( + resource_group_name=RESOURCE_GROUP_NAME, + name="default", + diagnostic_name="memory", + ) + assert result.name == "memory" + assert result.status == "healthy" + + +def test_legacy_create_or_replace_optional_body_without_body(client): + result = client.legacy.create_or_replace_optional_body( + resource_group_name=RESOURCE_GROUP_NAME, + configuration_name="default", + ) + assert result.name == "default" + assert result.properties.config_value == "default-value" + assert result.properties.provisioning_state == "Succeeded" + + +def test_legacy_create_or_replace_optional_body_with_body(client): + result = client.legacy.create_or_replace_optional_body( + resource_group_name=RESOURCE_GROUP_NAME, + configuration_name="default", + resource=models.Configuration( + location="eastus", + properties=models.ConfigurationProperties(config_value="custom-value"), + ), + ) + assert result.name == "default" + assert result.properties.config_value == "custom-value" + assert result.properties.provisioning_state == "Succeeded" + + +def test_paging_post_action_paging(client): + items = list( + client.paging.post_action_paging( + resource_group_name=RESOURCE_GROUP_NAME, + monitor_name="monitor1", + body=models.LogStatusRequest(filter="status eq 'active'"), + ) + ) + assert len(items) == 1 + assert ( + items[0].id + == f"/subscriptions/{SUBSCRIPTION_ID}/resourceGroups/{RESOURCE_GROUP_NAME}/providers/Microsoft.Compute/virtualMachines/vm1" + ) + assert items[0].sending_metrics is True + + +def test_paging_mark_as_pageable(client): + items = list( + client.paging.mark_as_pageable( + resource_group_name=RESOURCE_GROUP_NAME, + monitor_name="monitor1", + ) + ) + assert len(items) == 2 + assert items[0].name == "collection1" + assert items[0].properties.display_name == "Test Collection" + assert items[1].name == "collection2" + assert items[1].properties.display_name == "Another Collection" diff --git a/packages/http-client-python/tests/mock_api/azure/test_azure_client_generator_core_api_version_client_api_versions.py b/packages/http-client-python/tests/mock_api/azure/test_azure_client_generator_core_api_version_client_api_versions.py new file mode 100644 index 00000000000..5d4c4f65896 --- /dev/null +++ b/packages/http-client-python/tests/mock_api/azure/test_azure_client_generator_core_api_version_client_api_versions.py @@ -0,0 +1,17 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +from azure.clientgenerator.core.apiversion.clientapiversions import ClientApiVersionsClient + + +@pytest.fixture +def client(): + with ClientApiVersionsClient(endpoint="http://localhost:3000", api_version="2022-10-01") as client: + yield client + + +def test_send_api_version(client: ClientApiVersionsClient): + client.send_api_version()