From 0ec729eb6d77352e0296e63d9ca1778260e968d0 Mon Sep 17 00:00:00 2001 From: huimiu Date: Thu, 13 Aug 2026 13:13:51 +0800 Subject: [PATCH] test: route Projects recording traffic through proxy --- .../workflows/test-ext-azure-ai-agents.yml | 8 ++-- .../internal/azure/client_options.go | 12 +++++- .../internal/azure/client_options_test.go | 28 ++++++++++++ .../internal/pkg/recordproxy/transport.go | 11 +++++ .../pkg/recordproxy/transport_record.go | 43 +++++++++++++++++++ ...est_AIAgent_Init_NoPrompt_WithProject.yaml | 15 +++++++ 6 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 cli/azd/extensions/azure.ai.projects/internal/azure/client_options_test.go create mode 100644 cli/azd/extensions/azure.ai.projects/internal/pkg/recordproxy/transport.go create mode 100644 cli/azd/extensions/azure.ai.projects/internal/pkg/recordproxy/transport_record.go diff --git a/.github/workflows/test-ext-azure-ai-agents.yml b/.github/workflows/test-ext-azure-ai-agents.yml index 3e49ed23fd3..93a25ab4789 100644 --- a/.github/workflows/test-ext-azure-ai-agents.yml +++ b/.github/workflows/test-ext-azure-ai-agents.yml @@ -125,7 +125,7 @@ jobs: - name: Build & install projects dependency from source working-directory: cli/azd/extensions/azure.ai.projects run: | - pwsh -File ci-build.ps1 -OutputFileName azure-ai-projects-linux-amd64 -Version "$(cat version.txt | tr -d '\r\n')" + pwsh -File ci-build.ps1 -BuildRecordMode -OutputFileName azure-ai-projects-linux-amd64 -Version "$(cat version.txt | tr -d '\r\n')" mkdir -p bin mv azure-ai-projects-linux-amd64 bin/ azd x pack @@ -164,10 +164,12 @@ jobs: - name: Swap extension to record binary for Tier 1 run: | - # Overwrite the installed production binary with the record-tagged - # binary so Tier 1 tests can intercept/replay HTTP traffic. + # Replace installed production binaries with record-tagged binaries + # so Tier 1 tests can intercept and replay HTTP traffic. cp "${{ github.workspace }}/cli/azd/extensions/azure.ai.agents/azure-ai-agents-linux-amd64-record" \ ~/.azd/extensions/azure.ai.agents/azure-ai-agents-linux-amd64 + cp "${{ github.workspace }}/cli/azd/extensions/azure.ai.projects/azure-ai-projects-linux-amd64-record" \ + ~/.azd/extensions/azure.ai.projects/azure-ai-projects-linux-amd64 - name: Run Tier 1 tests (playback, record extension) working-directory: cli/azd diff --git a/cli/azd/extensions/azure.ai.projects/internal/azure/client_options.go b/cli/azd/extensions/azure.ai.projects/internal/azure/client_options.go index 105f15ba71c..461c4ddeee1 100644 --- a/cli/azd/extensions/azure.ai.projects/internal/azure/client_options.go +++ b/cli/azd/extensions/azure.ai.projects/internal/azure/client_options.go @@ -6,7 +6,9 @@ package azure import ( "fmt" + "net/http" + "azure.ai.projects/internal/pkg/recordproxy" "azure.ai.projects/internal/version" "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" @@ -20,7 +22,7 @@ func NewArmClientOptions() *arm.ClientOptions { "azd-ext-azure-ai-projects/%s", version.Version, ) - return &arm.ClientOptions{ + options := &arm.ClientOptions{ ClientOptions: policy.ClientOptions{ Logging: policy.LogOptions{ AllowedHeaders: []string{ @@ -33,4 +35,12 @@ func NewArmClientOptions() *arm.ClientOptions { }, }, } + + if recordproxy.Transport != nil { + options.ClientOptions.Transport = &http.Client{ + Transport: recordproxy.Transport, + } + } + + return options } diff --git a/cli/azd/extensions/azure.ai.projects/internal/azure/client_options_test.go b/cli/azd/extensions/azure.ai.projects/internal/azure/client_options_test.go new file mode 100644 index 00000000000..3ca93d5d29f --- /dev/null +++ b/cli/azd/extensions/azure.ai.projects/internal/azure/client_options_test.go @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azure + +import ( + "net/http" + "testing" + + "azure.ai.projects/internal/pkg/recordproxy" + + "github.com/stretchr/testify/require" +) + +func TestNewArmClientOptionsUsesRecordTransport(t *testing.T) { + transport := &http.Transport{} + previous := recordproxy.Transport + recordproxy.Transport = transport + t.Cleanup(func() { + recordproxy.Transport = previous + }) + + options := NewArmClientOptions() + + client, ok := options.ClientOptions.Transport.(*http.Client) + require.True(t, ok) + require.Same(t, transport, client.Transport) +} diff --git a/cli/azd/extensions/azure.ai.projects/internal/pkg/recordproxy/transport.go b/cli/azd/extensions/azure.ai.projects/internal/pkg/recordproxy/transport.go new file mode 100644 index 00000000000..505996bd6b5 --- /dev/null +++ b/cli/azd/extensions/azure.ai.projects/internal/pkg/recordproxy/transport.go @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +//go:build !record + +package recordproxy + +import "net/http" + +// Transport is nil in non-record builds. +var Transport http.RoundTripper diff --git a/cli/azd/extensions/azure.ai.projects/internal/pkg/recordproxy/transport_record.go b/cli/azd/extensions/azure.ai.projects/internal/pkg/recordproxy/transport_record.go new file mode 100644 index 00000000000..cd7ccd5a48e --- /dev/null +++ b/cli/azd/extensions/azure.ai.projects/internal/pkg/recordproxy/transport_record.go @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +//go:build record + +package recordproxy + +import ( + "crypto/tls" + "net/http" + "net/url" + "os" +) + +// Transport routes requests through the recording proxy. +var Transport http.RoundTripper + +func init() { + value, ok := os.LookupEnv("AZD_TEST_HTTPS_PROXY") + if !ok { + return + } + + proxyURL, err := url.Parse(value) + if err != nil { + panic("recordproxy: invalid AZD_TEST_HTTPS_PROXY URL: " + err.Error()) + } + + base, ok := http.DefaultTransport.(*http.Transport) + if !ok { + panic("recordproxy: http.DefaultTransport is not *http.Transport") + } + + transport := base.Clone() + transport.TLSClientConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + InsecureSkipVerify: true, //nolint:gosec + } + transport.Proxy = http.ProxyURL(proxyURL) + + http.DefaultTransport = transport + Transport = transport +} diff --git a/cli/azd/test/functional/ai_agents/testdata/recordings/Test_AIAgent_Init_NoPrompt_WithProject.yaml b/cli/azd/test/functional/ai_agents/testdata/recordings/Test_AIAgent_Init_NoPrompt_WithProject.yaml index ce8a9594e3d..5b38dc1e69c 100644 --- a/cli/azd/test/functional/ai_agents/testdata/recordings/Test_AIAgent_Init_NoPrompt_WithProject.yaml +++ b/cli/azd/test/functional/ai_agents/testdata/recordings/Test_AIAgent_Init_NoPrompt_WithProject.yaml @@ -3362,6 +3362,21 @@ interactions: status: 200 OK code: 200 duration: 1.4071139s + - id: 47 + request: + url: https://management.azure.com:443/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test-agents-recording-0000000000000000000/providers/Microsoft.CognitiveServices/accounts/test-ai-account-000/projects/test-proj0?api-version=2025-06-01 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 988 + uncompressed: false + body: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test-agents-recording-0000000000000000000/providers/Microsoft.CognitiveServices/accounts/test-ai-account-000/projects/test-proj0","name":"test-ai-account-000/test-proj0","type":"Microsoft.CognitiveServices/accounts/projects","etag":"\"3f01e98b-0000-0a00-0000-6a38cf830000\"","location":"canadacentral","kind":"AIServices","properties":{"endpoints":{"AI Foundry API":"https://test-ai-account-000.services.ai.azure.com/api/projects/test-proj0"},"isDefault":true,"internalId":"e764be7a68dc4a3bb1bc75bde375f40d","provisioningState":"Succeeded"},"identity":{"principalId":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","tenantId":"11111111-1111-1111-1111-111111111111","type":"SystemAssigned"},"systemData":{"createdBy":"test-user@email.com","createdByType":"User","createdAt":"2026-06-22T06:00:28.8975386Z","lastModifiedBy":"test-user@email.com","lastModifiedByType":"User","lastModifiedAt":"2026-06-22T06:00:28.8975386Z"}}' + status: 200 OK + code: 200 --- model_deployment: gpt-4o project_id: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test-agents-recording-0000000000000000000/providers/Microsoft.CognitiveServices/accounts/test-ai-account-000/projects/test-proj0