From 1fc2ad114c6a6cc6494b5fdf45a241a4358265e8 Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Mon, 6 Apr 2026 17:05:07 +0000 Subject: [PATCH 1/3] feat: add AZD_DEPLOY_{SERVICE}_IGNORE_SLOTS env var to bypass slot deployment When deploying to an App Service with deployment slots, azd automatically routes deployments to slots instead of the main app. This prevents users from deploying directly to the main app in scenarios like dual-branch pipelines (e.g., one branch deploys to production, another to a staging slot). This change introduces a per-service environment variable AZD_DEPLOY_{SERVICE}_IGNORE_SLOTS that, when set to a truthy boolean value, bypasses all slot detection logic and deploys directly to the main app. - Takes precedence over AZD_DEPLOY_{SERVICE}_SLOT_NAME with a warning - Supports all strconv.ParseBool truthy values (true, TRUE, 1, t, T) - Documented in environment-variables.md alongside the existing SLOT_NAME var Fixes #7365 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/docs/environment-variables.md | 11 + .../pkg/project/service_target_appservice.go | 31 +++ .../project/service_target_appservice_test.go | 220 ++++++++++++++++++ 3 files changed, 262 insertions(+) diff --git a/cli/azd/docs/environment-variables.md b/cli/azd/docs/environment-variables.md index f85fcdc540f..9b04997e241 100644 --- a/cli/azd/docs/environment-variables.md +++ b/cli/azd/docs/environment-variables.md @@ -24,6 +24,17 @@ for each environment. | `AZURE_APP_SERVICE_DASHBOARD_URI` | The URI for the .NET Aspire dashboard hosted on Azure App Service. | | `AZURE_AKS_CLUSTER_NAME` | The name of the Azure Kubernetes Service cluster. | +### App Service Slot Deployments + +These variables control deployment slot behavior for Azure App Service targets. In all variable names, +`{SERVICE}` is the uppercase service name from `azure.yaml` with hyphens replaced by underscores +(e.g., service `my-api` → `MY_API`). + +| Variable | Description | +| --- | --- | +| `AZD_DEPLOY_{SERVICE}_SLOT_NAME` | When multiple deployment slots exist, auto-selects the named slot instead of prompting. The value must match an existing slot name. | +| `AZD_DEPLOY_{SERVICE}_IGNORE_SLOTS` | If true, bypasses all slot detection logic and deploys directly to the main app, even when deployment slots exist. Takes precedence over `AZD_DEPLOY_{SERVICE}_SLOT_NAME`. | + ## Dev Center Variables Variables for [Azure Dev Center](https://learn.microsoft.com/azure/dev-box/overview-what-is-microsoft-dev-box) diff --git a/cli/azd/pkg/project/service_target_appservice.go b/cli/azd/pkg/project/service_target_appservice.go index 03a3454c027..7479bd95d7e 100644 --- a/cli/azd/pkg/project/service_target_appservice.go +++ b/cli/azd/pkg/project/service_target_appservice.go @@ -6,7 +6,9 @@ package project import ( "context" "fmt" + "log" "os" + "strconv" "strings" "github.com/azure/azure-dev/cli/azd/internal/mapper" @@ -245,6 +247,25 @@ func (st *appServiceTarget) determineDeploymentTargets( targetResource *environment.TargetResource, progress *async.Progress[ServiceProgress], ) ([]deploymentTarget, error) { + // Check if slot deployment is explicitly disabled for this service + ignoreSlotsEnvVar := ignoreSlotsEnvVarNameForService(serviceConfig.Name) + if ignoreSlots, err := strconv.ParseBool( + st.env.Getenv(ignoreSlotsEnvVar), + ); err == nil && ignoreSlots { + // Warn if SLOT_NAME env var is also set, since it will be ignored + slotEnvVar := slotEnvVarNameForService(serviceConfig.Name) + if slotName := st.env.Getenv(slotEnvVar); slotName != "" { + log.Printf( + "WARNING: %s is set but will be ignored because %s is enabled", + slotEnvVar, ignoreSlotsEnvVar, + ) + } + + progress.SetProgress(NewServiceProgress( + "Skipping slot deployment (deploying to main app)")) + return []deploymentTarget{{SlotName: ""}}, nil + } + progress.SetProgress(NewServiceProgress("Checking deployment history")) // Check if there are previous deployments @@ -339,6 +360,16 @@ func slotEnvVarNameForService(serviceName string) string { return fmt.Sprintf("AZD_DEPLOY_%s_SLOT_NAME", normalizedName) } +// ignoreSlotsEnvVarNameForService returns the environment variable name for opting out of +// automatic slot deployment for a given service. The format is AZD_DEPLOY_{SERVICE_NAME}_IGNORE_SLOTS +// where the service name is uppercase and any hyphens are replaced with underscores. +// When set to a truthy boolean value, azd deploys directly to the main app, ignoring any +// configured deployment slots. +func ignoreSlotsEnvVarNameForService(serviceName string) string { + normalizedName := strings.ToUpper(strings.ReplaceAll(serviceName, "-", "_")) + return fmt.Sprintf("AZD_DEPLOY_%s_IGNORE_SLOTS", normalizedName) +} + // Gets the exposed endpoints for the App Service, including any deployment slots func (st *appServiceTarget) Endpoints( ctx context.Context, diff --git a/cli/azd/pkg/project/service_target_appservice_test.go b/cli/azd/pkg/project/service_target_appservice_test.go index 65a438217c6..4c25141806e 100644 --- a/cli/azd/pkg/project/service_target_appservice_test.go +++ b/cli/azd/pkg/project/service_target_appservice_test.go @@ -4,11 +4,18 @@ package project import ( + "context" + "net/http" "strings" "testing" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2" + "github.com/azure/azure-dev/cli/azd/pkg/async" "github.com/azure/azure-dev/cli/azd/pkg/azapi" "github.com/azure/azure-dev/cli/azd/pkg/environment" + "github.com/azure/azure-dev/cli/azd/test/mocks" + "github.com/azure/azure-dev/cli/azd/test/mocks/mockaccount" "github.com/stretchr/testify/require" ) @@ -53,3 +60,216 @@ func TestNewAppServiceTargetTypeValidation(t *testing.T) { }) } } + +func TestSlotEnvVarNameForService(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + serviceName string + expected string + }{ + "SimpleService": { + serviceName: "api", + expected: "AZD_DEPLOY_API_SLOT_NAME", + }, + "HyphenatedService": { + serviceName: "my-web-app", + expected: "AZD_DEPLOY_MY_WEB_APP_SLOT_NAME", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result := slotEnvVarNameForService(tc.serviceName) + require.Equal(t, tc.expected, result) + }) + } +} + +func TestIgnoreSlotsEnvVarNameForService(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + serviceName string + expected string + }{ + "SimpleService": { + serviceName: "api", + expected: "AZD_DEPLOY_API_IGNORE_SLOTS", + }, + "HyphenatedService": { + serviceName: "my-web-app", + expected: "AZD_DEPLOY_MY_WEB_APP_IGNORE_SLOTS", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result := ignoreSlotsEnvVarNameForService(tc.serviceName) + require.Equal(t, tc.expected, result) + }) + } +} + +func TestDetermineDeploymentTargets_IgnoreSlots(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + envVars map[string]string + hasDeployments bool + slots []string + expectedSlots []string // empty string = main app + }{ + "IgnoreSlots_True_NoSlots": { + envVars: map[string]string{"AZD_DEPLOY_API_IGNORE_SLOTS": "true"}, + hasDeployments: true, + slots: []string{}, + expectedSlots: []string{""}, + }, + "IgnoreSlots_True_OneSlot": { + envVars: map[string]string{"AZD_DEPLOY_API_IGNORE_SLOTS": "true"}, + hasDeployments: true, + slots: []string{"staging"}, + expectedSlots: []string{""}, + }, + "IgnoreSlots_True_MultipleSlots": { + envVars: map[string]string{"AZD_DEPLOY_API_IGNORE_SLOTS": "true"}, + hasDeployments: true, + slots: []string{"staging", "preview"}, + expectedSlots: []string{""}, + }, + "IgnoreSlots_True_FirstDeployment_WithSlots": { + envVars: map[string]string{"AZD_DEPLOY_API_IGNORE_SLOTS": "true"}, + hasDeployments: false, + slots: []string{"staging"}, + expectedSlots: []string{""}, + }, + "IgnoreSlots_True_OverridesSlotName": { + envVars: map[string]string{ + "AZD_DEPLOY_API_IGNORE_SLOTS": "true", + "AZD_DEPLOY_API_SLOT_NAME": "staging", + }, + hasDeployments: true, + slots: []string{"staging", "preview"}, + expectedSlots: []string{""}, + }, + "IgnoreSlots_False_OneSlot_SubsequentDeploy": { + envVars: map[string]string{"AZD_DEPLOY_API_IGNORE_SLOTS": "false"}, + hasDeployments: true, + slots: []string{"staging"}, + expectedSlots: []string{"staging"}, + }, + "IgnoreSlots_Unset_OneSlot_SubsequentDeploy": { + envVars: map[string]string{}, + hasDeployments: true, + slots: []string{"staging"}, + expectedSlots: []string{"staging"}, + }, + "IgnoreSlots_Unset_NoSlots_SubsequentDeploy": { + envVars: map[string]string{}, + hasDeployments: true, + slots: []string{}, + expectedSlots: []string{""}, + }, + "IgnoreSlots_Unset_FirstDeploy_WithSlots": { + envVars: map[string]string{}, + hasDeployments: false, + slots: []string{"staging"}, + expectedSlots: []string{"", "staging"}, + }, + "IgnoreSlots_TrueNumeric": { + envVars: map[string]string{"AZD_DEPLOY_API_IGNORE_SLOTS": "1"}, + hasDeployments: true, + slots: []string{"staging"}, + expectedSlots: []string{""}, + }, + "IgnoreSlots_TrueUppercase": { + envVars: map[string]string{"AZD_DEPLOY_API_IGNORE_SLOTS": "TRUE"}, + hasDeployments: true, + slots: []string{"staging"}, + expectedSlots: []string{""}, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + mockContext := mocks.NewMockContext(t.Context()) + azCli := azapi.NewAzureClient( + mockaccount.SubscriptionCredentialProviderFunc( + func(_ context.Context, _ string) (azcore.TokenCredential, error) { + return mockContext.Credentials, nil + }, + ), + mockContext.ArmClientOptions, + ) + + // Mock deployment history + mockContext.HttpClient.When(func(request *http.Request) bool { + return request.Method == http.MethodGet && + strings.Contains(request.URL.Path, "/deployments") + }).RespondFn(func(request *http.Request) (*http.Response, error) { + var deployments []*armappservice.Deployment + if tc.hasDeployments { + deployments = []*armappservice.Deployment{ + {ID: new("dep-1"), Name: new("dep-1")}, + } + } + response := armappservice.WebAppsClientListDeploymentsResponse{ + DeploymentCollection: armappservice.DeploymentCollection{ + Value: deployments, + }, + } + return mocks.CreateHttpResponseWithBody( + request, http.StatusOK, response) + }) + + // Mock slots + mockContext.HttpClient.When(func(request *http.Request) bool { + return request.Method == http.MethodGet && + strings.Contains(request.URL.Path, "/slots") + }).RespondFn(func(request *http.Request) (*http.Response, error) { + sites := make([]*armappservice.Site, len(tc.slots)) + for i, slot := range tc.slots { + fullName := "WEB_APP_NAME/" + slot + sites[i] = &armappservice.Site{Name: &fullName} + } + response := armappservice.WebAppsClientListSlotsResponse{ + WebAppCollection: armappservice.WebAppCollection{ + Value: sites, + }, + } + return mocks.CreateHttpResponseWithBody( + request, http.StatusOK, response) + }) + + env := environment.NewWithValues("test", tc.envVars) + target := &appServiceTarget{ + env: env, + cli: azCli, + console: mockContext.Console, + } + + serviceConfig := &ServiceConfig{Name: "api"} + targetResource := environment.NewTargetResource( + "SUB_ID", "RG_ID", "WEB_APP_NAME", + string(azapi.AzureResourceTypeWebSite), + ) + progress := async.NewNoopProgress[ServiceProgress]() + + targets, err := target.determineDeploymentTargets( + *mockContext.Context, + serviceConfig, + targetResource, + progress, + ) + + require.NoError(t, err) + require.Len(t, targets, len(tc.expectedSlots)) + for i, expected := range tc.expectedSlots { + require.Equal(t, expected, targets[i].SlotName) + } + }) + } +} From 06dfb49e9443ba019e344e63762b52384495d456 Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Mon, 6 Apr 2026 18:13:41 +0000 Subject: [PATCH 2/3] fix: address PR review feedback - Use LookupEnv to distinguish unset vs invalid values, warn on bad booleans - Use console.MessageUxItem with ux.WarningMessage for user-visible warnings instead of log.Printf (which is suppressed unless debug mode is enabled) - Use environment.Key() to normalize service names instead of duplicating logic - Move docs section from "Core Azure Variables" to "General Configuration" near AZD_DEPLOY_TIMEOUT where deployment-related env vars belong - Add test case for invalid boolean value falling back to slot logic Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/docs/environment-variables.md | 22 +++++----- .../pkg/project/service_target_appservice.go | 44 +++++++++++-------- .../project/service_target_appservice_test.go | 6 +++ 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/cli/azd/docs/environment-variables.md b/cli/azd/docs/environment-variables.md index 9b04997e241..3af740096e2 100644 --- a/cli/azd/docs/environment-variables.md +++ b/cli/azd/docs/environment-variables.md @@ -24,17 +24,6 @@ for each environment. | `AZURE_APP_SERVICE_DASHBOARD_URI` | The URI for the .NET Aspire dashboard hosted on Azure App Service. | | `AZURE_AKS_CLUSTER_NAME` | The name of the Azure Kubernetes Service cluster. | -### App Service Slot Deployments - -These variables control deployment slot behavior for Azure App Service targets. In all variable names, -`{SERVICE}` is the uppercase service name from `azure.yaml` with hyphens replaced by underscores -(e.g., service `my-api` → `MY_API`). - -| Variable | Description | -| --- | --- | -| `AZD_DEPLOY_{SERVICE}_SLOT_NAME` | When multiple deployment slots exist, auto-selects the named slot instead of prompting. The value must match an existing slot name. | -| `AZD_DEPLOY_{SERVICE}_IGNORE_SLOTS` | If true, bypasses all slot detection logic and deploys directly to the main app, even when deployment slots exist. Takes precedence over `AZD_DEPLOY_{SERVICE}_SLOT_NAME`. | - ## Dev Center Variables Variables for [Azure Dev Center](https://learn.microsoft.com/azure/dev-box/overview-what-is-microsoft-dev-box) @@ -63,6 +52,17 @@ integration. | `AZD_BUILDER_IMAGE` | The builder docker image used to perform Dockerfile-less builds. | | `AZD_DEPLOY_TIMEOUT` | Timeout for deployment operations, parsed as an integer number of seconds (for example, `1200`). Defaults to `1200` seconds (20 minutes). | +### App Service Slot Deployments + +These variables control deployment slot behavior for Azure App Service targets. In all variable names, +`{SERVICE}` is the uppercase service name from `azure.yaml` with hyphens replaced by underscores +(e.g., service `my-api` → `MY_API`). + +| Variable | Description | +| --- | --- | +| `AZD_DEPLOY_{SERVICE}_SLOT_NAME` | When multiple deployment slots exist, auto-selects the named slot instead of prompting. The value must match an existing slot name. | +| `AZD_DEPLOY_{SERVICE}_IGNORE_SLOTS` | If true, bypasses all slot detection logic and deploys directly to the main app, even when deployment slots exist. Takes precedence over `AZD_DEPLOY_{SERVICE}_SLOT_NAME`. | + ## Extension Variables These variables are set and consumed by azd extension hosts (for example, IDE/editor integrations) diff --git a/cli/azd/pkg/project/service_target_appservice.go b/cli/azd/pkg/project/service_target_appservice.go index 7479bd95d7e..500451e86a6 100644 --- a/cli/azd/pkg/project/service_target_appservice.go +++ b/cli/azd/pkg/project/service_target_appservice.go @@ -6,7 +6,6 @@ package project import ( "context" "fmt" - "log" "os" "strconv" "strings" @@ -16,6 +15,7 @@ import ( "github.com/azure/azure-dev/cli/azd/pkg/azapi" "github.com/azure/azure-dev/cli/azd/pkg/environment" "github.com/azure/azure-dev/cli/azd/pkg/input" + "github.com/azure/azure-dev/cli/azd/pkg/output/ux" "github.com/azure/azure-dev/cli/azd/pkg/tools" ) @@ -249,21 +249,29 @@ func (st *appServiceTarget) determineDeploymentTargets( ) ([]deploymentTarget, error) { // Check if slot deployment is explicitly disabled for this service ignoreSlotsEnvVar := ignoreSlotsEnvVarNameForService(serviceConfig.Name) - if ignoreSlots, err := strconv.ParseBool( - st.env.Getenv(ignoreSlotsEnvVar), - ); err == nil && ignoreSlots { - // Warn if SLOT_NAME env var is also set, since it will be ignored - slotEnvVar := slotEnvVarNameForService(serviceConfig.Name) - if slotName := st.env.Getenv(slotEnvVar); slotName != "" { - log.Printf( - "WARNING: %s is set but will be ignored because %s is enabled", - slotEnvVar, ignoreSlotsEnvVar, - ) - } + if ignoreSlotsValue, hasIgnoreSlots := st.env.LookupEnv(ignoreSlotsEnvVar); hasIgnoreSlots { + ignoreSlots, err := strconv.ParseBool(ignoreSlotsValue) + if err != nil { + st.console.MessageUxItem(ctx, &ux.WarningMessage{ + Description: fmt.Sprintf( + "Ignoring invalid value %q for %s; expected a boolean value", + ignoreSlotsValue, ignoreSlotsEnvVar), + }) + } else if ignoreSlots { + // Warn if SLOT_NAME env var is also set, since it will be ignored + slotEnvVar := slotEnvVarNameForService(serviceConfig.Name) + if slotName := st.env.Getenv(slotEnvVar); slotName != "" { + st.console.MessageUxItem(ctx, &ux.WarningMessage{ + Description: fmt.Sprintf( + "%s is set but will be ignored because %s is enabled", + slotEnvVar, ignoreSlotsEnvVar), + }) + } - progress.SetProgress(NewServiceProgress( - "Skipping slot deployment (deploying to main app)")) - return []deploymentTarget{{SlotName: ""}}, nil + progress.SetProgress(NewServiceProgress( + "Skipping slot deployment (deploying to main app)")) + return []deploymentTarget{{SlotName: ""}}, nil + } } progress.SetProgress(NewServiceProgress("Checking deployment history")) @@ -356,8 +364,7 @@ func (st *appServiceTarget) determineDeploymentTargets( // for a given service. The format is AZD_DEPLOY_{SERVICE_NAME}_SLOT_NAME where the service name // is uppercase and any hyphens are replaced with underscores. func slotEnvVarNameForService(serviceName string) string { - normalizedName := strings.ToUpper(strings.ReplaceAll(serviceName, "-", "_")) - return fmt.Sprintf("AZD_DEPLOY_%s_SLOT_NAME", normalizedName) + return fmt.Sprintf("AZD_DEPLOY_%s_SLOT_NAME", environment.Key(serviceName)) } // ignoreSlotsEnvVarNameForService returns the environment variable name for opting out of @@ -366,8 +373,7 @@ func slotEnvVarNameForService(serviceName string) string { // When set to a truthy boolean value, azd deploys directly to the main app, ignoring any // configured deployment slots. func ignoreSlotsEnvVarNameForService(serviceName string) string { - normalizedName := strings.ToUpper(strings.ReplaceAll(serviceName, "-", "_")) - return fmt.Sprintf("AZD_DEPLOY_%s_IGNORE_SLOTS", normalizedName) + return fmt.Sprintf("AZD_DEPLOY_%s_IGNORE_SLOTS", environment.Key(serviceName)) } // Gets the exposed endpoints for the App Service, including any deployment slots diff --git a/cli/azd/pkg/project/service_target_appservice_test.go b/cli/azd/pkg/project/service_target_appservice_test.go index 4c25141806e..bac7beb5fa3 100644 --- a/cli/azd/pkg/project/service_target_appservice_test.go +++ b/cli/azd/pkg/project/service_target_appservice_test.go @@ -189,6 +189,12 @@ func TestDetermineDeploymentTargets_IgnoreSlots(t *testing.T) { slots: []string{"staging"}, expectedSlots: []string{""}, }, + "IgnoreSlots_InvalidValue_FallsBackToSlotLogic": { + envVars: map[string]string{"AZD_DEPLOY_API_IGNORE_SLOTS": "notabool"}, + hasDeployments: true, + slots: []string{"staging"}, + expectedSlots: []string{"staging"}, + }, } for name, tc := range tests { From b0ef65fdbfc183878b10fe748cff89bec0072a5a Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Tue, 7 Apr 2026 05:34:12 +0000 Subject: [PATCH 3/3] fix: update doc comment and add warning assertions per review - Add IGNORE_SLOTS override as first bullet in determineDeploymentTargets doc comment so it documents the highest-precedence exit path - Assert warning messages in tests for OverridesSlotName and InvalidValue cases to catch regressions that silently drop WarningMessage output Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pkg/project/service_target_appservice.go | 4 +++ .../project/service_target_appservice_test.go | 34 +++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/cli/azd/pkg/project/service_target_appservice.go b/cli/azd/pkg/project/service_target_appservice.go index 500451e86a6..5064b42bc13 100644 --- a/cli/azd/pkg/project/service_target_appservice.go +++ b/cli/azd/pkg/project/service_target_appservice.go @@ -230,6 +230,10 @@ type deploymentTarget struct { // based on deployment history and available slots. // // Deployment Strategy: +// - Override — AZD_DEPLOY_{SERVICE_NAME}_IGNORE_SLOTS (highest precedence): +// When set to a truthy boolean value, bypasses all slot detection and deploys directly +// to the main app. If AZD_DEPLOY_{SERVICE_NAME}_SLOT_NAME is also set, it is ignored +// and a warning is emitted. // - First deployment (no history): // Deploy to main app AND all slots to ensure consistency across all environments. // This prevents configuration drift and ensures all slots start with the same baseline. diff --git a/cli/azd/pkg/project/service_target_appservice_test.go b/cli/azd/pkg/project/service_target_appservice_test.go index bac7beb5fa3..666326cedeb 100644 --- a/cli/azd/pkg/project/service_target_appservice_test.go +++ b/cli/azd/pkg/project/service_target_appservice_test.go @@ -115,10 +115,11 @@ func TestDetermineDeploymentTargets_IgnoreSlots(t *testing.T) { t.Parallel() tests := map[string]struct { - envVars map[string]string - hasDeployments bool - slots []string - expectedSlots []string // empty string = main app + envVars map[string]string + hasDeployments bool + slots []string + expectedSlots []string // empty string = main app + expectedWarning string // substring expected in console output, empty = no warning }{ "IgnoreSlots_True_NoSlots": { envVars: map[string]string{"AZD_DEPLOY_API_IGNORE_SLOTS": "true"}, @@ -149,9 +150,10 @@ func TestDetermineDeploymentTargets_IgnoreSlots(t *testing.T) { "AZD_DEPLOY_API_IGNORE_SLOTS": "true", "AZD_DEPLOY_API_SLOT_NAME": "staging", }, - hasDeployments: true, - slots: []string{"staging", "preview"}, - expectedSlots: []string{""}, + hasDeployments: true, + slots: []string{"staging", "preview"}, + expectedSlots: []string{""}, + expectedWarning: "AZD_DEPLOY_API_SLOT_NAME is set but will be ignored", }, "IgnoreSlots_False_OneSlot_SubsequentDeploy": { envVars: map[string]string{"AZD_DEPLOY_API_IGNORE_SLOTS": "false"}, @@ -190,10 +192,11 @@ func TestDetermineDeploymentTargets_IgnoreSlots(t *testing.T) { expectedSlots: []string{""}, }, "IgnoreSlots_InvalidValue_FallsBackToSlotLogic": { - envVars: map[string]string{"AZD_DEPLOY_API_IGNORE_SLOTS": "notabool"}, - hasDeployments: true, - slots: []string{"staging"}, - expectedSlots: []string{"staging"}, + envVars: map[string]string{"AZD_DEPLOY_API_IGNORE_SLOTS": "notabool"}, + hasDeployments: true, + slots: []string{"staging"}, + expectedSlots: []string{"staging"}, + expectedWarning: "Ignoring invalid value", }, } @@ -276,6 +279,15 @@ func TestDetermineDeploymentTargets_IgnoreSlots(t *testing.T) { for i, expected := range tc.expectedSlots { require.Equal(t, expected, targets[i].SlotName) } + + // Verify warning messages when expected + consoleOutput := strings.Join(mockContext.Console.Output(), "\n") + if tc.expectedWarning != "" { + require.Contains(t, consoleOutput, tc.expectedWarning) + } else { + require.Empty(t, mockContext.Console.Output(), + "unexpected warning in console output: %s", consoleOutput) + } }) } }