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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,4 @@ cli/azd/cover_*
review-*.diff

.playwright-mcp/
.worktrees/
27 changes: 20 additions & 7 deletions cli/azd/pkg/account/subscriptions_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,27 @@ func (m *SubscriptionsManager) getSubscriptions(ctx context.Context) (getSubscri

subscriptions, err := m.cache.Load(ctx, uid)
if err != nil {
subscriptions, err = m.ListSubscriptions(ctx)
if err != nil {
return getSubscriptionsResult{}, fmt.Errorf("listing subscriptions: %w", err)
}
// When running in playback mode with a synthetic subscription, skip the real ARM call
// to list subscriptions. The synthetic subscription is sufficient for the test to proceed,
// and the recording cassette won't contain the /tenants API responses needed by ListSubscriptions.
syntheticId := os.Getenv("AZD_DEBUG_SYNTHETIC_SUBSCRIPTION")
if syntheticId != "" {
subscriptions = []Subscription{{
Id: syntheticId,
Name: "AZD Synthetic Test Subscription",
TenantId: claims.TenantId,
UserAccessTenantId: claims.TenantId,
}}
Comment thread
jongio marked this conversation as resolved.
} else {
subscriptions, err = m.ListSubscriptions(ctx)
if err != nil {
return getSubscriptionsResult{}, fmt.Errorf("listing subscriptions: %w", err)
}
Comment thread
jongio marked this conversation as resolved.

err = m.cache.Save(ctx, uid, subscriptions)
if err != nil {
return getSubscriptionsResult{}, fmt.Errorf("saving subscriptions to cache: %w", err)
err = m.cache.Save(ctx, uid, subscriptions)
if err != nil {
return getSubscriptionsResult{}, fmt.Errorf("saving subscriptions to cache: %w", err)
}
}
}

Expand Down
36 changes: 36 additions & 0 deletions cli/azd/pkg/account/subscriptions_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,42 @@ func (c *staticSubCache) Clear(ctx context.Context) error {
return nil
}

func TestSubscriptionsManager_SyntheticSubscription_SkipsListOnCacheMiss(t *testing.T) {
syntheticSubId := "SYNTHETIC-SUB-ID-123"
t.Setenv("AZD_DEBUG_SYNTHETIC_SUBSCRIPTION", syntheticSubId)

listSubscriptionsCalled := false
mockHttp := mockhttp.NewMockHttpUtil()
// If ListSubscriptions is called, it would hit this handler. We set a flag to detect it.
mockHttp.When(func(request *http.Request) bool {
return true
}).RespondFn(func(request *http.Request) (*http.Response, error) {
listSubscriptionsCalled = true
return &http.Response{
Request: request,
StatusCode: http.StatusInternalServerError,
Body: io.NopCloser(bytes.NewBufferString(`{"error": "should not be called"}`)),
}, nil
})

subManager := &SubscriptionsManager{
service: NewSubscriptionsService(
&mocks.MockMultiTenantCredentialProvider{},
armClientOptions(mockHttp),
),
cache: &BypassSubscriptionsCache{},
principalInfo: &principalInfoProviderMock{},
console: mockinput.NewMockConsole(),
}

result, err := subManager.getSubscriptions(t.Context())
require.NoError(t, err)
require.False(t, listSubscriptionsCalled, "ListSubscriptions should not be called when synthetic subscription is set")
require.Len(t, result.subscriptions, 1)
require.Equal(t, syntheticSubId, result.subscriptions[0].Id)
require.Equal(t, "AZD Synthetic Test Subscription", result.subscriptions[0].Name)
}

func TestSubscriptionsManager_GetSubscription_PreservesTenantFields(t *testing.T) {
t.Parallel()

Expand Down
5 changes: 4 additions & 1 deletion cli/azd/test/azdcli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ func NewCLI(t *testing.T, opts ...Options) *CLI {
credentialServer.Close()
})

cli.Env = append(cli.Env, fmt.Sprintf("AZD_AUTH_ENDPOINT=%s", credentialServer.URL))
cli.Env = append(cli.Env,
fmt.Sprintf("AZD_AUTH_ENDPOINT=%s", credentialServer.URL),
"AZD_AUTH_KEY=playback-test-key",
)
}
}

Expand Down
6 changes: 5 additions & 1 deletion cli/azd/test/recording/recording.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,11 @@ func Start(t *testing.T, opts ...Options) *Session {
strings.Contains(req.URL.Host, "azuresdkartifacts.z5.web.core.windows.net") ||
strings.Contains(req.URL.Host, "default.exp-tas.com") ||
(strings.Contains(req.URL.Host, "dev.azure.com") &&
strings.Contains(req.URL.Path, "/oidctoken"))
strings.Contains(req.URL.Path, "/oidctoken")) ||
// Passthrough requests to the local test credential server (AZD_AUTH_ENDPOINT).
// In playback mode, RemoteCredential calls this server for tokens.
(strings.Contains(req.URL.Host, "127.0.0.1") &&
req.URL.Path == "/token")
Comment thread
jongio marked this conversation as resolved.
})

proxy := &connectHandler{
Expand Down
Loading