File: ConfigHealingTests.cs
Web Access
Project: src\tests\Aspire.Cli.EndToEnd.Tests\Aspire.Cli.EndToEnd.Tests.csproj (Aspire.Cli.EndToEnd.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Cli.EndToEnd.Tests.Helpers;
using Aspire.Cli.Tests.Utils;
using Hex1b.Automation;
using Hex1b.Input;
using Xunit;
 
namespace Aspire.Cli.EndToEnd.Tests;
 
/// <summary>
/// End-to-end tests verifying that the CLI heals a corrupted aspire.config.json
/// (invalid apphost path, JSON comments) by auto-detecting the correct apphost
/// and updating the config file.
/// </summary>
public sealed class ConfigHealingTests(ITestOutputHelper output)
{
    /// <summary>
    /// Verifies that when aspire.config.json has an invalid apphost path and JSON comments,
    /// the CLI auto-detects the correct apphost, runs it successfully, and updates ("heals")
    /// the config file with the correct path.
    /// </summary>
    [Fact]
    public async Task InvalidAppHostPathWithComments_IsHealedOnRun()
    {
        var repoRoot = CliE2ETestHelpers.GetRepoRoot();
        var installMode = CliE2ETestHelpers.DetectDockerInstallMode(repoRoot);
 
        var workspace = TemporaryWorkspace.Create(output);
 
        using var terminal = CliE2ETestHelpers.CreateDockerTestTerminal(
            repoRoot, installMode, output,
            mountDockerSocket: true,
            workspace: workspace);
 
        var pendingRun = terminal.RunAsync(TestContext.Current.CancellationToken);
 
        var counter = new SequenceCounter();
        var auto = new Hex1bTerminalAutomator(terminal, defaultTimeout: TimeSpan.FromSeconds(500));
 
        await auto.PrepareDockerEnvironmentAsync(counter, workspace);
        await auto.InstallAspireCliInDockerAsync(installMode, counter);
 
        // 1. Create a starter project
        await auto.AspireNewAsync("HealTest", counter, useRedisCache: false);
 
        // 2. Overwrite aspire.config.json with an invalid apphost path and a JSON comment
        var configFilePath = Path.Combine(
            workspace.WorkspaceRoot.FullName,
            "HealTest",
            "aspire.config.json");
 
        var malformedConfig = """
            {
              // This comment should be handled gracefully
              "appHost": {
                "path": "nonexistent/path/to/AppHost.csproj" // this path doesn't exist
              },
              "channel": "stable"
            }
            """;
        File.WriteAllText(configFilePath, malformedConfig);
 
        // 3. Change into the project directory and run aspire run
        //    The CLI should detect the invalid path, find the real apphost,
        //    update the config, and start the app successfully
        await auto.TypeAsync("cd HealTest");
        await auto.EnterAsync();
        await auto.WaitForSuccessPromptAsync(counter);
        await auto.TypeAsync("aspire run");
        await auto.EnterAsync();
        await auto.WaitUntilTextAsync("Press CTRL+C to stop the apphost and exit.", timeout: TimeSpan.FromMinutes(3));
        await auto.Ctrl().KeyAsync(Hex1bKey.C);
        await auto.WaitForSuccessPromptAsync(counter);
 
        // 4. Verify the config file was healed (host-side file check)
        if (!File.Exists(configFilePath))
        {
            throw new InvalidOperationException(
                $"Config file does not exist after healing: {configFilePath}");
        }
 
        var content = File.ReadAllText(configFilePath);
 
        // The healed config should contain a valid apphost path
        // (pointing to the actual AppHost project)
        if (!content.Contains("HealTest.AppHost"))
        {
            throw new InvalidOperationException(
                $"Config file was not healed with correct AppHost path. Content:\n{content}");
        }
 
        // The invalid path should no longer be present
        if (content.Contains("nonexistent/path"))
        {
            throw new InvalidOperationException(
                $"Config file still contains invalid path after healing. Content:\n{content}");
        }
 
        await auto.TypeAsync("exit");
        await auto.EnterAsync();
 
        await pendingRun;
    }
}