Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions cli/azd/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement v1.1.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration v1.1.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v3 v3.1.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appplatform/armappplatform/v2 v2.0.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.3.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.8.0
Expand All @@ -28,7 +27,6 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.2
github.com/Azure/azure-storage-file-go v0.8.0
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/Masterminds/semver/v3 v3.4.0
Expand Down Expand Up @@ -87,7 +85,6 @@ require (
)

require (
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
Expand Down Expand Up @@ -122,7 +119,6 @@ require (
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mailru/easyjson v0.9.1 // indirect
github.com/mattn/go-ieproxy v0.0.12 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
Expand Down
17 changes: 0 additions & 17 deletions cli/azd/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkk
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/AssemblyAI/assemblyai-go-sdk v1.3.0 h1:AtOVgGxUycvK4P4ypP+1ZupecvFgnfH+Jsum0o5ILoU=
github.com/AssemblyAI/assemblyai-go-sdk v1.3.0/go.mod h1:H0naZbvpIW49cDA5ZZ/gggeXqi7ojSGB1mqshRk6kNE=
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
Expand All @@ -42,8 +39,6 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappcon
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration v1.1.1/go.mod h1:21Lewei+tg5zp5xmyOxfDY//2tBvWQXee0UoM8xZjr8=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v3 v3.1.0 h1:ilMZ576u8sm975EqV+AKEtD4u9TLwqEo2XY9csPXBRo=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v3 v3.1.0/go.mod h1:LGhzy+pg9AKr1Z7ZRyTC1qr1xNyVqLsqydvLdY+2iQk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appplatform/armappplatform/v2 v2.0.1 h1:hINezNnGCNxNRWLA8uzb4srfqhPnVnZ/XO7v9B0YMfA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appplatform/armappplatform/v2 v2.0.1/go.mod h1:iFAEO9giU3p1Ly0K3p6xMMXUqMMk6f//tXQ8OL5gK8Q=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.3.0 h1:JI8PcWOImyvIUEZ0Bbmfe05FOlWkMi2KhjG+cAKaUms=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.3.0/go.mod h1:nJLFPGJkyKfDDyJiPuHIXsCi/gpJkm07EvRgiX7SGlI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 h1:Hp+EScFOu9HeCbeW8WU2yQPJd4gGwhMgKxWe+G6jNzw=
Expand Down Expand Up @@ -90,8 +85,6 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZY
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk=
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.2 h1:l3SabZmNuXCMCbQUIeR4W6/N4j8SeH/lwX+a6leZhHo=
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.2/go.mod h1:k+mEZ4f1pVqZTRqtSDW2AhZ/3wT5qLpsUA75C/k7dtE=
github.com/Azure/azure-storage-file-go v0.8.0 h1:OX8DGsleWLUE6Mw4R/OeWEZMvsTIpwN94J59zqKQnTI=
github.com/Azure/azure-storage-file-go v0.8.0/go.mod h1:3w3mufGcMjcOJ3w+4Gs+5wsSgkT7xDwWWqMMIrXtW4c=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
Expand Down Expand Up @@ -274,7 +267,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
Expand All @@ -291,10 +283,6 @@ github.com/mark3labs/mcp-go v0.41.1/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZ
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
github.com/mattn/go-ieproxy v0.0.12 h1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M=
github.com/mattn/go-ieproxy v0.0.12/go.mod h1:Vn+N61199DAnVeTgaF8eoB9PvLO8P3OBnG95ENh7B7c=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
Expand Down Expand Up @@ -483,8 +471,6 @@ golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
Expand All @@ -500,8 +486,6 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand All @@ -518,7 +502,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
Expand Down
25 changes: 20 additions & 5 deletions cli/azd/pkg/azsdk/storage/storage_blob_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ import (

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/azure/azure-dev/cli/azd/pkg/account"
"github.com/azure/azure-dev/cli/azd/pkg/auth"
"github.com/azure/azure-dev/cli/azd/pkg/cloud"
)

// AccountConfig contains the configuration for connecting to a storage account
type AccountConfig struct {
AccountName string
ContainerName string
Endpoint string
AccountName string
ContainerName string
Endpoint string
SubscriptionId string
}

var (
Expand Down Expand Up @@ -172,6 +174,7 @@ func NewBlobSdkClient(
accountConfig *AccountConfig,
coreClientOptions *azcore.ClientOptions,
cloud *cloud.Cloud,
tenantResolver account.SubscriptionTenantResolver,
) (*azblob.Client, error) {
blobOptions := &azblob.ClientOptions{
ClientOptions: *coreClientOptions,
Expand All @@ -181,8 +184,20 @@ func NewBlobSdkClient(
accountConfig.Endpoint = cloud.StorageEndpointSuffix
}

// Use home tenant ID
credential, err := credentialProvider.GetTokenCredential(context.Background(), "")
// Determine which tenant to use for authentication
tenantId := ""
if accountConfig.SubscriptionId != "" {
// If a subscription ID is configured, resolve the tenant ID for that subscription
resolvedTenantId, err := tenantResolver.LookupTenant(context.Background(), accountConfig.SubscriptionId)
if err != nil {
return nil, fmt.Errorf(
"failed to resolve tenant for subscription '%s': %w", accountConfig.SubscriptionId, err)
}
tenantId = resolvedTenantId
}
// Otherwise, use home tenant ID (empty string)

credential, err := credentialProvider.GetTokenCredential(context.Background(), tenantId)
if err != nil {
return nil, err
}
Expand Down
155 changes: 155 additions & 0 deletions cli/azd/pkg/azsdk/storage/storage_blob_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package storage

import (
"context"
"errors"
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/azure/azure-dev/cli/azd/pkg/account"
"github.com/azure/azure-dev/cli/azd/pkg/cloud"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

// mockMultiTenantCredentialProvider is a mock implementation for testing
type mockMultiTenantCredentialProvider struct {
mock.Mock
}

func (m *mockMultiTenantCredentialProvider) GetTokenCredential(
ctx context.Context, tenantId string) (azcore.TokenCredential, error) {
args := m.Called(ctx, tenantId)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(azcore.TokenCredential), args.Error(1)
}

// mockSubscriptionTenantResolver is a mock implementation for testing
type mockSubscriptionTenantResolver struct {
mock.Mock
}

var _ account.SubscriptionTenantResolver = (*mockSubscriptionTenantResolver)(nil)

func (m *mockSubscriptionTenantResolver) LookupTenant(
ctx context.Context, subscriptionId string) (string, error) {
args := m.Called(ctx, subscriptionId)
return args.String(0), args.Error(1)
}

// mockTokenCredential is a minimal mock implementation for testing
type mockTokenCredential struct {
mock.Mock
}

func (m *mockTokenCredential) GetToken(
ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
args := m.Called(ctx, opts)
return args.Get(0).(azcore.AccessToken), args.Error(1)
}

func Test_NewBlobSdkClient_UsesHomeTenantWhenNoSubscriptionId(t *testing.T) {
mockCredProvider := &mockMultiTenantCredentialProvider{}
mockTenantResolver := &mockSubscriptionTenantResolver{}
mockCred := &mockTokenCredential{}

accountConfig := &AccountConfig{
AccountName: "testaccount",
ContainerName: "testcontainer",
// No SubscriptionId set - should use home tenant
}

coreClientOptions := &azcore.ClientOptions{}

// Expect credential provider to be called with empty tenant ID (home tenant)
mockCredProvider.On("GetTokenCredential", mock.Anything, "").Return(mockCred, nil)

client, err := NewBlobSdkClient(
mockCredProvider,
accountConfig,
coreClientOptions,
cloud.AzurePublic(),
mockTenantResolver,
)

require.NoError(t, err)
require.NotNil(t, client)
mockCredProvider.AssertExpectations(t)

// TenantResolver should NOT be called when no subscription ID is provided
mockTenantResolver.AssertNotCalled(t, "LookupTenant", mock.Anything, mock.Anything)
}

func Test_NewBlobSdkClient_ResolvesTenantWhenSubscriptionIdProvided(t *testing.T) {
mockCredProvider := &mockMultiTenantCredentialProvider{}
mockTenantResolver := &mockSubscriptionTenantResolver{}
mockCred := &mockTokenCredential{}

testSubscriptionId := "test-subscription-id"
testTenantId := "test-tenant-id"

accountConfig := &AccountConfig{
AccountName: "testaccount",
ContainerName: "testcontainer",
SubscriptionId: testSubscriptionId,
}

coreClientOptions := &azcore.ClientOptions{}

// Expect tenant resolver to be called with the subscription ID
mockTenantResolver.On("LookupTenant", mock.Anything, testSubscriptionId).Return(testTenantId, nil)

// Expect credential provider to be called with resolved tenant ID
mockCredProvider.On("GetTokenCredential", mock.Anything, testTenantId).Return(mockCred, nil)

client, err := NewBlobSdkClient(
mockCredProvider,
accountConfig,
coreClientOptions,
cloud.AzurePublic(),
mockTenantResolver,
)

require.NoError(t, err)
require.NotNil(t, client)
mockCredProvider.AssertExpectations(t)
mockTenantResolver.AssertExpectations(t)
}

func Test_NewBlobSdkClient_ReturnsErrorWhenTenantResolutionFails(t *testing.T) {
mockCredProvider := &mockMultiTenantCredentialProvider{}
mockTenantResolver := &mockSubscriptionTenantResolver{}

testSubscriptionId := "test-subscription-id"

accountConfig := &AccountConfig{
AccountName: "testaccount",
ContainerName: "testcontainer",
SubscriptionId: testSubscriptionId,
}

coreClientOptions := &azcore.ClientOptions{}

// Simulate tenant resolution failure
mockTenantResolver.On("LookupTenant", mock.Anything, testSubscriptionId).
Return("", errors.New("subscription not found"))
Comment on lines +140 to +141
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the third-party github.com/pkg/errors package for new code goes against modern Go best practices. Since Go 1.13, the standard library's errors package and fmt.Errorf with %w provide equivalent functionality. For consistency and to avoid unnecessary dependencies, use errors.New("subscription not found") instead, and import the standard errors package.

Copilot uses AI. Check for mistakes.

client, err := NewBlobSdkClient(
mockCredProvider,
accountConfig,
coreClientOptions,
cloud.AzurePublic(),
mockTenantResolver,
)

require.Error(t, err)
require.Nil(t, client)
require.Contains(t, err.Error(), "failed to resolve tenant for subscription")
mockTenantResolver.AssertExpectations(t)
}
17 changes: 17 additions & 0 deletions cli/azd/pkg/environment/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/azure/azure-dev/cli/azd/pkg/account"
"github.com/azure/azure-dev/cli/azd/pkg/auth"
"github.com/azure/azure-dev/cli/azd/pkg/azsdk/storage"
"github.com/azure/azure-dev/cli/azd/pkg/cloud"
Expand Down Expand Up @@ -380,6 +381,12 @@ func registerContainerComponents(t *testing.T, mockContext *mocks.MockContext) {
mockContext.Container.MustRegisterSingleton(func() *azcore.ClientOptions {
return mockContext.CoreClientOptions
})

// Register a mock SubscriptionTenantResolver for tests
mockContext.Container.MustRegisterSingleton(func() account.SubscriptionTenantResolver {
return &mockSubscriptionTenantResolver{}
})

mockContext.Container.MustRegisterSingleton(storage.NewBlobSdkClient)
mockContext.Container.MustRegisterSingleton(config.NewManager)
mockContext.Container.MustRegisterSingleton(storage.NewBlobClient)
Expand All @@ -405,6 +412,16 @@ func registerContainerComponents(t *testing.T, mockContext *mocks.MockContext) {
})
}

// mockSubscriptionTenantResolver is a simple mock for testing
type mockSubscriptionTenantResolver struct{}

var _ account.SubscriptionTenantResolver = (*mockSubscriptionTenantResolver)(nil)

func (m *mockSubscriptionTenantResolver) LookupTenant(ctx context.Context, subscriptionId string) (string, error) {
// For tests, just return empty string (home tenant)
return "", nil
}

type MockDataStore struct {
mock.Mock
}
Expand Down
21 changes: 20 additions & 1 deletion cli/azd/test/functional/remote_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/azure/azure-dev/cli/azd/pkg/account"
"github.com/azure/azure-dev/cli/azd/pkg/auth"
"github.com/azure/azure-dev/cli/azd/pkg/azsdk/storage"
"github.com/azure/azure-dev/cli/azd/pkg/cloud"
Expand Down Expand Up @@ -91,14 +92,32 @@ func createBlobClient(
)
require.NoError(t, err)

// Create a mock SubscriptionTenantResolver that returns empty tenant (home tenant)
tenantResolver := &mockTenantResolver{}

sdkClient, err := storage.NewBlobSdkClient(
auth.NewMultiTenantCredentialProvider(authManager), storageConfig, coreClientOptions, cloud.AzurePublic())
auth.NewMultiTenantCredentialProvider(authManager),
storageConfig,
coreClientOptions,
cloud.AzurePublic(),
tenantResolver,
)
require.NoError(t, err)
require.NotNil(t, sdkClient)

return storage.NewBlobClient(storageConfig, sdkClient)
}

// mockTenantResolver is a simple mock implementation for testing
type mockTenantResolver struct{}

var _ account.SubscriptionTenantResolver = (*mockTenantResolver)(nil)

func (m *mockTenantResolver) LookupTenant(ctx context.Context, subscriptionId string) (string, error) {
// For tests, just return empty string (home tenant)
return "", nil
}

type remoteStateTestFunc func(storageConfig *storage.AccountConfig)

func runTestWithRemoteState(t *testing.T, testFunc remoteStateTestFunc) {
Expand Down