File: AzureAppServiceTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Azure.Tests\Aspire.Hosting.Azure.Tests.csproj (Aspire.Hosting.Azure.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#pragma warning disable ASPIRECOMPUTE002
#pragma warning disable ASPIREPIPELINES001
#pragma warning disable ASPIREAZURE003
 
using System.Text.Json.Nodes;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Foundry;
using Aspire.Hosting.Pipelines;
using Aspire.Hosting.Utils;
using Aspire.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using static Aspire.Hosting.Utils.AzureManifestUtils;
 
namespace Aspire.Hosting.Azure.Tests;
 
public class AzureAppServiceTests(ITestOutputHelper outputHelper)
{
    [Fact]
    public async Task AddContainerAppEnvironmentAddsDeploymentTargetWithContainerAppToProjectResources()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var env = builder.AddAzureAppServiceEnvironment("env");
 
        builder.AddProject<Project>("api", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints()
            .PublishAsAzureAppServiceWebsite((infrastructure, site) =>
            {
                site.SiteConfig.IsWebSocketsEnabled = true;
            });
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var container = Assert.IsType<IComputeResource>(Assert.Single(model.GetProjectResources()), exactMatch: false);
 
        var target = container.GetDeploymentTargetAnnotation();
 
        Assert.NotNull(target);
        Assert.Same(env.Resource, target.ComputeEnvironment);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddContainerAppEnvironmentAddsEnvironmentResource()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var environment = Assert.Single(model.Resources.OfType<AzureAppServiceEnvironmentResource>());
 
        var (manifest, bicep) = await GetManifestWithBicep(environment);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceWithDelegatedSubnet()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var vnet = builder.AddAzureVirtualNetwork("vnet");
        var subnet = vnet.AddSubnet("app-service-subnet", "10.0.0.0/24");
        builder.AddAzureAppServiceEnvironment("env")
            .WithDelegatedSubnet(subnet)
            .WithDeploymentSlot("stage");
 
        var project = builder.AddProject<Project>("api", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
        var appServiceEnvironment = Assert.Single(model.Resources.OfType<AzureAppServiceEnvironmentResource>());
        var target = project.Resource.GetDeploymentTargetAnnotation();
        Assert.NotNull(target);
        var website = Assert.IsAssignableFrom<AzureProvisioningResource>(target.DeploymentTarget);
 
        var (_, virtualNetworkBicep) = await GetManifestWithBicep(vnet.Resource);
        var (_, environmentBicep) = await GetManifestWithBicep(appServiceEnvironment);
        var (_, websiteBicep) = await GetManifestWithBicep(website);
 
        await Verify($"""
            // Virtual network
            {virtualNetworkBicep}
 
            // App Service environment
            {environmentBicep}
 
            // App Service website
            {websiteBicep}
            """, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceWithDelegatedSubnetWithoutDeploymentSlot()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var vnet = builder.AddAzureVirtualNetwork("vnet");
        var subnet = vnet.AddSubnet("app-service-subnet", "10.0.0.0/24");
        builder.AddAzureAppServiceEnvironment("env")
            .WithDelegatedSubnet(subnet);
 
        var project = builder.AddProject<Project>("api", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
        var appServiceEnvironment = Assert.Single(model.Resources.OfType<AzureAppServiceEnvironmentResource>());
        var target = project.Resource.GetDeploymentTargetAnnotation();
        Assert.NotNull(target);
        var website = Assert.IsAssignableFrom<AzureProvisioningResource>(target.DeploymentTarget);
 
        var (_, virtualNetworkBicep) = await GetManifestWithBicep(vnet.Resource);
        var (_, environmentBicep) = await GetManifestWithBicep(appServiceEnvironment);
        var (_, websiteBicep) = await GetManifestWithBicep(website);
 
        await Verify($"""
            // Virtual network
            {virtualNetworkBicep}
 
            // App Service environment
            {environmentBicep}
 
            // App Service website
            {websiteBicep}
            """, "bicep");
    }
 
    [Fact]
    public async Task PublishAsAzureAppServiceWebsite_CanOverrideEnvironmentDelegatedSubnet()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var vnet = builder.AddAzureVirtualNetwork("vnet");
        var subnet = vnet.AddSubnet("app-service-subnet", "10.0.0.0/24");
        builder.AddAzureAppServiceEnvironment("env")
            .WithDelegatedSubnet(subnet)
            .WithDeploymentSlot("stage");
 
        var project = builder.AddProject<Project>("api", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints()
            .PublishAsAzureAppServiceWebsite(
                configure: (_, website) => website.VirtualNetworkSubnetId = new global::Azure.Core.ResourceIdentifier("/subscriptions/subscription/resourceGroups/resource-group/providers/Microsoft.Network/virtualNetworks/vnet/subnets/website-subnet"),
                configureSlot: (_, slot) => slot.VirtualNetworkSubnetId = new global::Azure.Core.ResourceIdentifier("/subscriptions/subscription/resourceGroups/resource-group/providers/Microsoft.Network/virtualNetworks/vnet/subnets/slot-subnet"));
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var target = project.Resource.GetDeploymentTargetAnnotation();
        Assert.NotNull(target);
        var website = Assert.IsAssignableFrom<AzureProvisioningResource>(target.DeploymentTarget);
 
        var (_, bicep) = await GetManifestWithBicep(website);
 
        await Verify(bicep, "bicep");
    }
 
    [Fact]
    public async Task PublishToAppService_WithDashedConnectionStringName_FailsValidationInPipeline()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
 
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, workspace.Path, step: "validate-appservice-config-env");
 
        builder.Services.AddSingleton(outputHelper);
        builder.Services.AddSingleton<IPipelineActivityReporter, TestPipelineActivityReporter>();
 
        builder.AddAzureAppServiceEnvironment("env");
 
        var cs = builder.AddConnectionString("my-db", ReferenceExpression.Create($"Host=example"));
 
        builder.AddProject<Project>("api", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints()
            .WithReference(cs)
            .PublishAsAzureAppServiceWebsite(configure: (_, _) => { });
 
        using var app = builder.Build();
 
        // Get reference to the reporter before running the app (to avoid disposed ServiceProvider)
        var reporter = app.Services.GetRequiredService<IPipelineActivityReporter>() as TestPipelineActivityReporter;
        Assert.NotNull(reporter);
 
        // Run the app - validation happens during the pipeline execution
        // The pipeline catches exceptions and reports them via the activity reporter
        await app.RunAsync();
 
        // Verify the step completed with error
        Assert.Contains(reporter.CompletedSteps, step =>
            step.StepTitle == "validate-appservice-config-env" &&
            step.CompletionState == CompletionState.CompletedWithError);
 
        // Verify the error message was logged with details about the problematic connection string
        Assert.Contains(reporter.LoggedMessages, log =>
            log.Message.Contains("ConnectionStrings__my-db") &&
            log.LogLevel == LogLevel.Error);
    }
 
    [Fact]
    public async Task PublishToAppService_WithDashedConnectionStringName_CanBeIgnored()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
 
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, workspace.Path, step: "validate-appservice-config-env");
 
        builder.Services.AddSingleton(outputHelper);
        builder.Services.AddSingleton<IPipelineActivityReporter, TestPipelineActivityReporter>();
 
        builder.AddAzureAppServiceEnvironment("env");
 
        var cs = builder.AddConnectionString("my-db", ReferenceExpression.Create($"Host=example"));
 
        builder.AddProject<Project>("api", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints()
            .WithReference(cs)
            .PublishAsAzureAppServiceWebsite(configure: (_, _) => { })
            .SkipEnvironmentVariableNameChecks();
 
        using var app = builder.Build();
 
        var reporter = app.Services.GetRequiredService<IPipelineActivityReporter>() as TestPipelineActivityReporter;
        Assert.NotNull(reporter);
 
        await app.RunAsync();
 
        Assert.Contains(reporter.CompletedSteps, step =>
            step.StepTitle == "validate-appservice-config-env" &&
            step.CompletionState == CompletionState.Completed);
    }
 
    [Fact]
    public async Task KeyvaultReferenceHandling()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        var db = builder.AddAzureCosmosDB("mydb").WithAccessKeyAuthentication();
        db.AddCosmosDatabase("db");
 
        var kvName = builder.AddParameter("kvName");
        var sharedRg = builder.AddParameter("sharedRg");
 
        var existingKv = builder.AddAzureKeyVault("existingKv")
                                .PublishAsExisting(kvName, sharedRg);
 
        builder.AddProject<Project>("api", launchProfileName: null)
            .WithReference(db)
            .WithEnvironment("SECRET_VALUE", existingKv.GetSecret("secret"));
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var container = Assert.Single(model.GetProjectResources());
 
        container.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task EndpointReferencesAreResolvedAcrossProjects()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        // Add 2 projects with endpoints
        var project1 = builder.AddProject<Project>("project1", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
 
        var project2 = builder.AddProject<Project>("project2", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints()
            .WithReference(project1);
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        project2.Resource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task EndpointReferenceToFoundryHostedAgentIsResolvedAcrossComputeEnvironments()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var appServiceEnv = builder.AddAzureAppServiceEnvironment("env");
 
        var project = builder.AddFoundry("foundry")
            .AddProject("project");
 
        // The agent app is deployed to the Foundry project compute environment via AsHostedAgent.
        var agent = builder.AddProject<Project>("agent", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
        agent.AsHostedAgent(project, HostedAgentProtocol.Responses, "2.0.0");
 
        // The web app is deployed to App Service and references the Foundry hosted agent. The App
        // Service publisher must delegate endpoint resolution to the Foundry compute environment
        // rather than looking the agent up in its own (App Service) endpoint map. See issue #17749.
        // WithReference(agent) exercises the bare EndpointReference branch; the explicit
        // Property(Url) environment variable exercises the EndpointReferenceExpression branch.
        var web = builder.AddProject<Project>("web", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints()
            .WithComputeEnvironment(appServiceEnv)
            .WithReference(agent)
            .WithEnvironment("AGENT_URL", agent.GetEndpoint("http").Property(EndpointProperty.Url));
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        SetFoundryProjectOutputs(project.Resource);
 
        web.Resource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    private static void SetFoundryProjectOutputs(AzureCognitiveServicesProjectResource project)
    {
        project.Outputs["endpoint"] = "https://account.services.ai.azure.com/api/projects/my-project";
        project.Outputs["APPLICATION_INSIGHTS_CONNECTION_STRING"] = "";
        project.ProvisioningTaskCompletionSource?.TrySetResult();
    }
 
    [Fact]
    public async Task AzureAppServiceSupportBaitAndSwitchResources()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        builder.AddProject<Project>("api", launchProfileName: null)
            .PublishAsDockerFile()
            .WithHttpEndpoint(env: "PORT", targetPort: 80)
            .WithExternalHttpEndpoints();
 
        using var app = builder.Build();
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var container = Assert.Single(model.GetContainerResources());
 
        container.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddDockerfileWithAppServiceInfrastructureAddsDeploymentTargetWithAppServiceToContainerResources()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        var directory = Directory.CreateTempSubdirectory(".aspire-test");
 
        // Contents of the Dockerfile are not important for this test
        File.WriteAllText(Path.Combine(directory.FullName, "Dockerfile"), "");
 
        builder.AddDockerfile("api", directory.FullName)
               .WithHttpEndpoint(targetPort: 85, env: "PORT")
               .WithExternalHttpEndpoints();
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var container = Assert.Single(model.GetContainerResources());
 
        container.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task UnknownManifestExpressionProviderIsHandledWithAllocateParameter()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        var customProvider = new CustomManifestExpressionProvider();
 
        var apiProject = builder.AddProject<Project>("api", launchProfileName: null)
            .WithHttpEndpoint()
            .WithEnvironment(context =>
            {
                context.EnvironmentVariables["CUSTOM_VALUE"] = customProvider;
            })
            .WithExternalHttpEndpoints();
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
        var project = Assert.Single(model.GetProjectResources());
 
        project.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public void AzureAppServiceEnvironmentHasNameOutputReference()
    {
        var builder = TestDistributedApplicationBuilder.Create();
        var env = builder.AddAzureAppServiceEnvironment("env");
 
        // Verify that the NameOutputReference property exists and returns the expected value
        Assert.NotNull(env.Resource.NameOutputReference);
        Assert.Equal("name", env.Resource.NameOutputReference.Name);
        Assert.Same(env.Resource, env.Resource.NameOutputReference.Resource);
    }
 
    [Fact]
    public async Task AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var nameParameter = builder.AddParameter("appServicePlanName", "existing-plan-name");
        var resourceGroupParameter = builder.AddParameter("appServicePlanResourceGroup", "existing-rg");
 
        builder.AddAzureAppServiceEnvironment("env")
            .PublishAsExisting(nameParameter, resourceGroupParameter);
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var environment = Assert.Single(model.Resources.OfType<AzureAppServiceEnvironmentResource>());
 
        Assert.True(environment.IsExisting());
        Assert.True(environment.TryGetLastAnnotation<ExistingAzureResourceAnnotation>(out var annotation));
        Assert.NotNull(annotation);
 
        var (manifest, bicep) = await GetManifestWithBicep(environment);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AzureAppServiceEnvironmentCanPublishExistingAppServicePlan()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
 
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, workspace.Path);
 
        var nameParameter = builder.AddParameter("appServicePlanName", "existing-plan-name");
        var resourceGroupParameter = builder.AddParameter("appServicePlanResourceGroup", "existing-rg");
 
        builder.AddAzureAppServiceEnvironment("env")
            .PublishAsExisting(nameParameter, resourceGroupParameter);
 
        builder.AddProject<Project>("api", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
 
        using var app = builder.Build();
 
        await app.RunAsync();
 
        var mainBicepPath = Path.Combine(workspace.Path, "main.bicep");
        Assert.True(File.Exists(mainBicepPath), $"Expected publish to produce '{mainBicepPath}'.");
        var mainBicep = await File.ReadAllTextAsync(mainBicepPath);
 
        var envBicepPath = Path.Combine(workspace.Path, "env", "env.bicep");
        Assert.True(File.Exists(envBicepPath), $"Expected publish to produce '{envBicepPath}'.");
        var envBicep = await File.ReadAllTextAsync(envBicepPath);
 
        var apiBicepPath = Path.Combine(workspace.Path, "api", "api.bicep");
        Assert.True(File.Exists(apiBicepPath), $"Expected publish to produce '{apiBicepPath}'.");
        var apiBicep = await File.ReadAllTextAsync(apiBicepPath);
 
        await Verify(mainBicep, "bicep")
            .AppendContentAsFile(envBicep, "bicep")
            .AppendContentAsFile(apiBicep, "bicep");
    }
 
    [Fact]
    public async Task AsExisting_WithExplicitContainerRegistry_PublishGeneratesEnvWithExistingPlanAndRegistry()
    {
        // Existing App Service Plan + existing ACR should reference both pre-provisioned resources.
        // Without WithAcrPullIdentity, Aspire still emits a new identity and AcrPull role assignment
        // for image pulls. Disable the dashboard so the snapshot focuses on plan/ACR behavior.
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var planName = builder.AddParameter("appServicePlanName");
        var registryName = builder.AddParameter("registryName");
        var sharedResourceGroup = builder.AddParameter("sharedRg");
 
        var acr = builder.AddAzureContainerRegistry("acr")
            .AsExisting(registryName, sharedResourceGroup);
 
        var env = builder.AddAzureAppServiceEnvironment("env")
            .AsExisting(planName, sharedResourceGroup)
            .WithAzureContainerRegistry(acr)
            .WithDashboard(false);
 
        var (manifest, bicep) = await GetManifestWithBicep(env.Resource);
 
        await Verify(bicep, extension: "bicep")
            .AppendContentAsFile(manifest.ToString(), "json");
    }
 
    [Fact]
    public async Task WithAcrPullIdentity_PublishGeneratesEnvWithSuppliedIdentity()
    {
        // Greenfield App Service Plan, but the user has BYO'd a managed identity. The generated
        // Bicep should NOT declare an env_mi resource or an AcrPull role assignment - the
        // identity id/client id should flow into the env module via parameters and be emitted as
        // AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID/CLIENT_ID.
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var mi = builder.AddAzureUserAssignedIdentity("shared-mi");
 
        var env = builder.AddAzureAppServiceEnvironment("env")
            .WithAcrPullIdentity(mi);
 
        var (manifest, bicep) = await GetManifestWithBicep(env.Resource);
 
        await Verify(bicep, extension: "bicep")
            .AppendContentAsFile(manifest.ToString(), "json");
    }
 
    [Fact]
    public async Task WithAcrPullIdentity_AsExisting_PublishGeneratesEnvWithSuppliedIdentity()
    {
        // Existing App Service Plan + existing ACR + BYO identity that already has AcrPull on the ACR.
        // Disable the dashboard so the env module references the pre-provisioned resources without
        // emitting a new ACR-pull identity, AcrPull role assignment, dashboard, or dashboard contributor identity.
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var planName = builder.AddParameter("appServicePlanName");
        var registryName = builder.AddParameter("registryName");
        var identityName = builder.AddParameter("identityName");
        var sharedResourceGroup = builder.AddParameter("sharedRg");
 
        var acr = builder.AddAzureContainerRegistry("acr")
            .AsExisting(registryName, sharedResourceGroup);
 
        var mi = builder.AddAzureUserAssignedIdentity("shared-mi")
            .AsExisting(identityName, sharedResourceGroup);
 
        var env = builder.AddAzureAppServiceEnvironment("env")
            .AsExisting(planName, sharedResourceGroup)
            .WithAzureContainerRegistry(acr)
            .WithAcrPullIdentity(mi)
            .WithDashboard(false);
 
        var (manifest, bicep) = await GetManifestWithBicep(env.Resource);
 
        await Verify(bicep, extension: "bicep")
            .AppendContentAsFile(manifest.ToString(), "json");
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public async Task WithDashboardControlsDashboardUrlPrintStep(bool enableDashboard)
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var env = builder.AddAzureAppServiceEnvironment("env")
            .WithDashboard(enableDashboard);
 
        using var app = builder.Build();
 
        var steps = await CreateStepsAsync(app, env.Resource);
        var hasPrintDashboardUrlStep = steps.Any(s => s.Name == "print-dashboard-url-env");
 
        Assert.Equal(enableDashboard, hasPrintDashboardUrlStep);
    }
 
    [Fact]
    public void AzureAppServiceEnvironmentImplementsIAzureComputeEnvironmentResource()
    {
        var builder = TestDistributedApplicationBuilder.Create();
        var env = builder.AddAzureAppServiceEnvironment("env");
 
        Assert.IsAssignableFrom<IAzureComputeEnvironmentResource>(env.Resource);
        Assert.IsAssignableFrom<IComputeEnvironmentResource>(env.Resource);
    }
 
    [Fact]
    [ActiveIssue("https://github.com/microsoft/aspire/issues/11818", typeof(PlatformDetection), nameof(PlatformDetection.IsRunningFromAzdo))]
    public async Task PublishAsAzureAppServiceWebsite_ThrowsIfNoEnvironment()
    {
        static async Task RunTest(Action<IDistributedApplicationBuilder> action)
        {
            var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
            // Do not add AddAzureAppServiceEnvironment
 
            action(builder);
 
            using var app = builder.Build();
 
            var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => ExecuteBeforeStartHooksAsync(app, default));
 
            Assert.Contains("there are no 'AzureAppServiceEnvironmentResource' resources", ex.Message);
        }
 
        await RunTest(builder =>
            builder.AddProject<Projects.ServiceA>("ServiceA")
                .PublishAsAzureAppServiceWebsite((_, _) => { }));
 
        await RunTest(builder =>
            builder.AddContainer("api", "myimage")
                .PublishAsAzureAppServiceWebsite((_, _) => { }));
 
        await RunTest(builder =>
            builder.AddExecutable("exe", "path/to/executable", ".")
                .PublishAsDockerFile()
                .PublishAsAzureAppServiceWebsite((_, _) => { }));
    }
 
    [Fact]
    public async Task ValidateAzureAppService_DoesNotThrowInRunMode()
    {
        // Regression test for https://github.com/microsoft/aspire/issues/16940.
        // In run mode, AddAzureAppServiceEnvironment does not add the env resource to the model.
        // If a compute resource still ends up with an AzureAppServiceWebsiteCustomizationAnnotation
        // (e.g. via WithAnnotation), the validation step should not throw at 'aspire run' time —
        // PublishAs* customizations are only meaningful at publish/deploy time.
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        builder.AddContainer("api", "myimage")
            .WithAnnotation(new AzureAppServiceWebsiteCustomizationAnnotation((_, _) => { }));
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
    }
 
    [Fact]
    [ActiveIssue("https://github.com/microsoft/aspire/issues/11818", typeof(PlatformDetection), nameof(PlatformDetection.IsRunningFromAzdo))]
    public async Task MultipleAzureAppServiceEnvironmentsSupported()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
 
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, workspace.Path, step: "publish-manifest");
 
        var env1 = builder.AddAzureAppServiceEnvironment("env1");
        var env2 = builder.AddAzureAppServiceEnvironment("env2");
 
        builder.AddProject<Projects.ServiceA>("ServiceA")
            .WithExternalHttpEndpoints()
            .WithComputeEnvironment(env1);
 
        builder.AddProject<Projects.ServiceB>("ServiceB")
            .WithExternalHttpEndpoints()
            .WithComputeEnvironment(env2);
 
        using var app = builder.Build();
 
        // Publishing will stop the app when it is done
        await app.RunAsync();
 
        var verifySettings = new VerifySettings();
        verifySettings.ScrubLines(line => line.Contains("\"path\"") && line.Contains(".csproj"));
        await VerifyFile(
            Path.Combine(workspace.Path, "aspire-manifest.json"),
            verifySettings);
    }
 
    [Fact]
    public async Task ResourceWithProbes()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
 
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, workspace.Path);
 
        var env1 = builder.AddAzureAppServiceEnvironment("env");
 
#pragma warning disable ASPIREPROBES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
        builder
            .AddProject<Project>("project1", launchProfileName: null)
            .WithHttpsEndpoint()
            .WithExternalHttpEndpoints()
            .WithHttpProbe(ProbeType.Readiness, "/ready", initialDelaySeconds: 60) // This will be ignored
            .WithHttpProbe(ProbeType.Liveness, "/health");
#pragma warning restore ASPIREPROBES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var project = Assert.Single(model.GetProjectResources());
        var projectProvisioningResource = project.GetDeploymentTargetAnnotation()?.DeploymentTarget as AzureProvisioningResource;
        Assert.NotNull(projectProvisioningResource);
 
        var (_, projectBicep) = await GetManifestWithBicep(projectProvisioningResource);
 
        await Verify(projectBicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env").WithDashboard(false);
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var environment = Assert.Single(model.Resources.OfType<AzureAppServiceEnvironmentResource>());
 
        var (manifest, bicep) = await GetManifestWithBicep(environment);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceToEnvironmentWithoutDashboard()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env").WithDashboard(false);
 
        // Add 2 projects with endpoints
        var project1 = builder.AddProject<Project>("project1", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
 
        var project2 = builder.AddProject<Project>("project2", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints()
            .WithReference(project1);
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        project2.Resource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceWithArgs()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        // Add 2 projects with endpoints
        var project1 = builder.AddProject<Project>("project1", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
 
        var project2 = builder.AddProject<Project>("project2", launchProfileName: null)
            .WithHttpEndpoint()
            .WithArgs("--myarg", "myvalue")
            .WithExternalHttpEndpoints()
            .WithReference(project1);
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        project2.Resource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceWithTargetPort()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        // Add 2 projects with endpoints
        var project1 = builder.AddProject<Project>("project1", launchProfileName: null)
            .WithHttpsEndpoint(targetPort:8000)
            .WithHttpEndpoint(targetPort: 8000)
            .WithExternalHttpEndpoints();
 
        var project2 = builder.AddProject<Project>("project2", launchProfileName: null)
            .WithHttpEndpoint(targetPort:9000)
            .WithExternalHttpEndpoints()
            .WithReference(project1);
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        project2.Resource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
                .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task CanPreserveHttpSchemeUsingWithHttpsUpgrade()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env")
               .WithHttpsUpgrade(false);
 
        var project1 = builder.AddProject<Project>("project1", launchProfileName: null)
            .WithHttpsEndpoint(targetPort: 8000)
            .WithHttpEndpoint(targetPort: 8000)
            .WithExternalHttpEndpoints();
 
        // Add a second project that references project1 so we can verify
        // the resolved endpoint URLs use http:// (not upgraded to https://)
        var project2 = builder.AddProject<Project>("project2", launchProfileName: null)
            .WithHttpEndpoint(targetPort: 9000)
            .WithExternalHttpEndpoints()
            .WithReference(project1);
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        project2.Resource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
                .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceWithTargetPortMultipleEndpoints()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        // Add 2 projects with endpoints
        var project1 = builder.AddProject<Project>("project1", launchProfileName: null)
            .WithExternalHttpEndpoints();
 
        var project2 = builder.AddProject<Project>("project2", launchProfileName: null)
            .WithHttpsEndpoint(targetPort: 8000)
            .WithHttpEndpoint(targetPort: 8000)
            .WithExternalHttpEndpoints()
            .WithReference(project1);
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        project2.Resource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
                .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceWithMultipleTargetPortsThrowsNotSupportedException()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        // Add 2 projects with endpoints
        var project1 = builder.AddProject<Project>("project1", launchProfileName: null)
            .WithExternalHttpEndpoints();
 
        var project2 = builder.AddProject<Project>("project2", launchProfileName: null)
            .WithHttpsEndpoint(targetPort: 8000)
            .WithHttpEndpoint(targetPort: 8800)
            .WithExternalHttpEndpoints()
            .WithReference(project1);
 
        using var app = builder.Build();
 
        var outer = await Assert.ThrowsAsync<InvalidOperationException>(() => ExecuteBeforeStartHooksAsync(app, default));
        var ex = Assert.IsType<NotSupportedException>(outer.InnerException);
 
        Assert.Equal("App Service does not support resources with multiple external endpoints.", ex.Message);
    }
 
    [Fact]
    public async Task AddAppServiceWithMixedNullAndExplicitTargetPortsThrowsNotSupportedException()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        // Add project with one endpoint with null target port and one with explicit target port
        var project = builder.AddProject<Project>("project1", launchProfileName: null)
            .WithHttpEndpoint() // null target port (default)
            .WithHttpsEndpoint(targetPort: 8443) // explicit target port
            .WithExternalHttpEndpoints();
 
        using var app = builder.Build();
 
        var outer = await Assert.ThrowsAsync<InvalidOperationException>(() => ExecuteBeforeStartHooksAsync(app, default));
        var ex = Assert.IsType<NotSupportedException>(outer.InnerException);
 
        Assert.Equal("App Service does not support resources with multiple external endpoints.", ex.Message);
    }
 
    [Fact]
    public async Task AddAppServiceProjectWithoutTargetPortUsesContainerPort()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        // Add project with endpoints but no target port specified
        var project = builder.AddProject<Project>("project1", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        project.Resource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        // For project resources without explicit target port, should use container port reference
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceContainerWithoutTargetPortUsesDefaultPort()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        // Add container with endpoints but no target port specified
        var container = builder.AddDockerfile("container1", "./myimage")
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        container.Resource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        // For non-project resources without explicit target port, should default to 8000
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task GetHostAddressExpression()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var env = builder.AddAzureAppServiceEnvironment("env");
 
        var project = builder
            .AddProject<Project>("project1", launchProfileName: null)
            .WithHttpEndpoint();
 
        var endpointReferenceEx = env.Resource.GetHostAddressExpression(project.GetEndpoint("http"));
        Assert.NotNull(endpointReferenceEx);
 
        Assert.Equal("project1-{0}.azurewebsites.net", endpointReferenceEx.Format);
        var provider = Assert.Single(endpointReferenceEx.ValueProviders);
        var output = Assert.IsType<BicepOutputReference>(provider);
        Assert.Equal(env.Resource, output.Resource);
        Assert.Equal("webSiteSuffix", output.Name);
    }
 
    [Theory]
    [InlineData(EndpointProperty.Url, "https://project1-website123.azurewebsites.net")]
    [InlineData(EndpointProperty.Host, "project1-website123.azurewebsites.net")]
    [InlineData(EndpointProperty.IPV4Host, "project1-website123.azurewebsites.net")]
    [InlineData(EndpointProperty.Port, "443")]
    [InlineData(EndpointProperty.TargetPort, "5000")]
    [InlineData(EndpointProperty.Scheme, "https")]
    [InlineData(EndpointProperty.HostAndPort, "project1-website123.azurewebsites.net")]
    [InlineData(EndpointProperty.TlsEnabled, "True")]
    public async Task GetEndpointPropertyExpression_ReturnsAppServiceEndpointPropertyExpression(EndpointProperty property, string expected)
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var env = builder.AddAzureAppServiceEnvironment("env");
        env.Resource.Outputs["webSiteSuffix"] = "website123";
        env.Resource.ProvisioningTaskCompletionSource?.TrySetResult();
 
        var project = builder
            .AddProject<Project>("project1", launchProfileName: null)
            .WithEndpoint(port: 8080, targetPort: 5000, scheme: "http", name: "http", isExternal: true);
 
#pragma warning disable ASPIRECOMPUTE002
        var expression = env.Resource.GetEndpointPropertyExpression(project.GetEndpoint("http").Property(property));
#pragma warning restore ASPIRECOMPUTE002
 
        Assert.Equal(expected, await expression.GetValueAsync(default));
    }
 
    [Fact]
    public async Task AddAppServiceWithApplicationInsightsLocation()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env").WithAzureApplicationInsights("westus");
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var environment = Assert.Single(model.Resources.OfType<AzureAppServiceEnvironmentResource>());
 
        var (manifest, bicep) = await GetManifestWithBicep(environment);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceWithApplicationInsightsDefaultLocation()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env").WithAzureApplicationInsights();
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var environment = Assert.Single(model.Resources.OfType<AzureAppServiceEnvironmentResource>());
 
        var (manifest, bicep) = await GetManifestWithBicep(environment);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceWithApplicationInsightsNormalizesBicepIdentifiers()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env-1").WithAzureApplicationInsights();
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var environment = Assert.Single(model.Resources.OfType<AzureAppServiceEnvironmentResource>());
 
        var (_, bicep) = await GetManifestWithBicep(environment);
 
        await Verify(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceWithApplicationInsightsLocationParam()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var appInsightsParam = builder.AddParameter("appInsightsLocation", "westus");
        builder.AddAzureAppServiceEnvironment("env").WithAzureApplicationInsights(appInsightsParam);
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var environment = Assert.Single(model.Resources.OfType<AzureAppServiceEnvironmentResource>());
 
        var (manifest, bicep) = await GetManifestWithBicep(environment);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceWithExistingApplicationInsights()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var appInsights = builder.AddAzureApplicationInsights("existingAppInsights");
        builder.AddAzureAppServiceEnvironment("env").WithAzureApplicationInsights(appInsights);
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var environment = Assert.Single(model.Resources.OfType<AzureAppServiceEnvironmentResource>());
 
        var (manifest, bicep) = await GetManifestWithBicep(environment);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceProjectWithApplicationInsightsSetsAppSettings()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env").WithAzureApplicationInsights();
 
        // Add project with endpoints but no target port specified
        var project = builder.AddProject<Project>("project1", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        project.Resource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        // For project resources without explicit target port, should use container port reference
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceWithDeploymentSlot()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env").WithDeploymentSlot("stage");
 
        // Add project with endpoints but no target port specified
        var project = builder.AddProject<Project>("project1", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        project.Resource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        // For project resources without explicit target port, should use container port reference
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task AddAppServiceWithDeploymentSlotParameter()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var slotParam = builder.AddParameter("deploymentSlot", "stage");
        builder.AddAzureAppServiceEnvironment("env").WithDeploymentSlot(slotParam);
 
        // Add project with endpoints but no target port specified
        var project = builder.AddProject<Project>("project1", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        project.Resource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
 
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        // For project resources without explicit target port, should use container port reference
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    [Fact]
    public async Task ConditionalExpressionWithParameterCondition()
    {
        var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        builder.AddAzureAppServiceEnvironment("env");
 
        var featureFlag = builder.AddParameter("enable-feature");
 
        var project = builder.AddProject<Project>("api", launchProfileName: null)
            .WithHttpEndpoint()
            .WithExternalHttpEndpoints();
 
        project.WithEnvironment(context =>
        {
            var conditional = ReferenceExpression.CreateConditional(
                featureFlag.Resource,
                bool.TrueString,
                ReferenceExpression.Create($"enabled"),
                ReferenceExpression.Create($"disabled"));
 
            context.EnvironmentVariables["FEATURE_MODE"] = conditional;
        });
 
        using var app = builder.Build();
 
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
        var proj = Assert.Single(model.GetProjectResources());
 
        proj.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);
        var resource = target?.DeploymentTarget as AzureProvisioningResource;
 
        Assert.NotNull(resource);
 
        var (manifest, bicep) = await GetManifestWithBicep(resource);
 
        await Verify(manifest.ToString(), "json")
              .AppendContentAsFile(bicep, "bicep");
    }
 
    private static Task<(JsonNode ManifestNode, string BicepText)> GetManifestWithBicep(IResource resource) =>
        AzureManifestUtils.GetManifestWithBicep(resource, skipPreparer: true);
 
    private static async Task<List<PipelineStep>> CreateStepsAsync(DistributedApplication app, AzureAppServiceEnvironmentResource resource)
    {
        var pipelineContext = new PipelineContext(
            app.Services.GetRequiredService<DistributedApplicationModel>(),
            new DistributedApplicationExecutionContext(DistributedApplicationOperation.Publish),
            app.Services,
            NullLogger.Instance,
            CancellationToken.None);
 
        var results = new List<PipelineStep>();
        foreach (var annotation in resource.Annotations.OfType<PipelineStepAnnotation>())
        {
            results.AddRange(await annotation.CreateStepsAsync(new PipelineStepFactoryContext
            {
                PipelineContext = pipelineContext,
                Resource = resource
            }));
        }
 
        return results;
    }
 
    private sealed class Project : IProjectMetadata
    {
        public string ProjectPath => "/foo/bar/project.csproj";
    }
 
    private sealed class CustomManifestExpressionProvider : IManifestExpressionProvider
    {
        public string ValueExpression => "{customValue}";
    }
}