File: AzureManifestUtils.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.
 
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json.Nodes;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
using Aspire.Hosting.Tests.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
 
namespace Aspire.Hosting.Utils;
 
public sealed class AzureManifestUtils
{
    public static Task<(JsonNode ManifestNode, string BicepText)> GetManifestWithBicep(IResource resource, bool skipPreparer = false) =>
        GetManifestWithBicep(new DistributedApplicationModel([resource]), resource, skipPreparer);
 
    public static Task<(JsonNode ManifestNode, string BicepText)> GetManifestWithBicep(DistributedApplicationModel appModel, IResource resource) =>
        GetManifestWithBicep(appModel, resource, skipPreparer: false);
 
    private static async Task<(JsonNode ManifestNode, string BicepText)> GetManifestWithBicep(DistributedApplicationModel appModel, IResource resource, bool skipPreparer)
    {
        if (!skipPreparer)
        {
            var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Publish);
            var azurePreparer = new AzureResourcePreparer(Options.Create(new AzureProvisioningOptions()), executionContext);
            await azurePreparer.OnBeforeStartAsync(new BeforeStartEvent(new TestServiceProvider(), appModel), cancellationToken: default);
        }
 
        string manifestDir = Directory.CreateTempSubdirectory(resource.Name).FullName;
        var manifestNode = await ManifestUtils.GetManifest(resource, manifestDir);
 
        if (!manifestNode.AsObject().TryGetPropertyValue("path", out var pathNode))
        {
            throw new ArgumentException("Specified resource does not contain a path property.", nameof(resource));
        }
 
        if (pathNode?.ToString() is not { } path || !File.Exists(Path.Combine(manifestDir, path)))
        {
            throw new ArgumentException("Path node in resource is null, empty, or does not exist.", nameof(resource));
        }
 
        var bicepText = await File.ReadAllTextAsync(Path.Combine(manifestDir, path));
        return (manifestNode, bicepText);
    }
 
    public static async Task VerifyAllAzureBicep(IDistributedApplicationBuilder builder)
    {
        await using var app = builder.Build();
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
        await ExecuteBeforeStartHooksAsync(app, default);
 
        // Collect bicep for all Azure provisioning resources, ordered by name for deterministic output
        var azureResources = model.Resources
            .OfType<AzureProvisioningResource>()
            .OrderBy(r => r.Name)
            .ToList();
 
        var sb = new StringBuilder();
 
        foreach (var resource in azureResources)
        {
            var (_, bicep) = await GetManifestWithBicep(resource, skipPreparer: true);
 
            sb.AppendLine($"// Resource: {resource.Name}");
            sb.AppendLine(bicep);
            sb.AppendLine();
        }
 
        await Verify(sb.ToString(), extension: "bicep")
            .ScrubLinesWithReplace(s => s.Replace("\\r\\n", "\\n"));
    }
 
    [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ExecuteBeforeStartHooksAsync")]
    public static extern Task ExecuteBeforeStartHooksAsync(DistributedApplication app, CancellationToken cancellationToken);
}