File: Utils\AppHostArgumentRedactorTests.cs
Web Access
Project: src\tests\Aspire.Cli.Tests\Aspire.Cli.Tests.csproj (Aspire.Cli.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.Utils;
 
namespace Aspire.Cli.Tests.Utils;
 
public class AppHostArgumentRedactorTests
{
    [Fact]
    public void Redact_NoSeparator_ReturnsArgumentsUnchanged()
    {
        string[] args = ["run", "--project", "AppHost.csproj"];
 
        Assert.Equal(args, AppHostArgumentRedactor.Redact(args));
    }
 
    [Fact]
    public void Redact_EmptyArguments_ReturnsEmpty()
    {
        Assert.Empty(AppHostArgumentRedactor.Redact([]));
    }
 
    [Fact]
    public void Redact_SeparatorWithTrailingTokens_RedactsEveryForwardedToken()
    {
        string[] args = ["run", "--project", "AppHost.csproj", "--", "--ConnectionStrings:db", "Server=db;Password=hunter2"];
 
        Assert.Equal(
            ["run", "--project", "AppHost.csproj", "--", "<redacted>", "<redacted>"],
            AppHostArgumentRedactor.Redact(args));
    }
 
    [Fact]
    public void Redact_SeparatorAsLastToken_KeepsSeparatorAndRedactsNothing()
    {
        string[] args = ["run", "--"];
 
        Assert.Equal(["run", "--"], AppHostArgumentRedactor.Redact(args));
    }
 
    [Fact]
    public void Redact_SeparatorAsOnlyToken_ReturnsSeparator()
    {
        Assert.Equal(["--"], AppHostArgumentRedactor.Redact(["--"]));
    }
 
    [Fact]
    public void Redact_LiteralSeparatorAfterFirstSeparator_IsRedacted()
    {
        // Only the first "--" is the CLI/AppHost boundary. A later "--" is AppHost input, so it is
        // redacted like every other forwarded token.
        string[] args = ["run", "--", "--secret", "--", "value"];
 
        Assert.Equal(
            ["run", "--", "<redacted>", "<redacted>", "<redacted>"],
            AppHostArgumentRedactor.Redact(args));
    }
 
    [Fact]
    public void Redact_SeparatorAsFirstToken_RedactsRemainder()
    {
        string[] args = ["--", "--ApiKey", "sk-live-secret"];
 
        Assert.Equal(["--", "<redacted>", "<redacted>"], AppHostArgumentRedactor.Redact(args));
    }
 
    [Fact]
    public void Redact_TokenContainingSeparatorAsSubstring_IsNotTreatedAsSeparator()
    {
        // "--foo" and "--=x" are not the bare separator, so nothing is redacted.
        string[] args = ["run", "--foo", "--=x"];
 
        Assert.Equal(["run", "--foo", "--=x"], AppHostArgumentRedactor.Redact(args));
    }
 
    [Fact]
    public void RedactToString_JoinsRedactedTokensWithSpaces()
    {
        string[] args = ["run", "--project", "AppHost.csproj", "--", "--ApiKey", "sk-live-secret"];
 
        Assert.Equal(
            "run --project AppHost.csproj -- <redacted> <redacted>",
            AppHostArgumentRedactor.RedactToString(args));
    }
 
    [Fact]
    public void RedactToString_NoSeparator_JoinsArgumentsVerbatim()
    {
        Assert.Equal("run --project AppHost.csproj", AppHostArgumentRedactor.RedactToString(["run", "--project", "AppHost.csproj"]));
    }
 
    [Fact]
    public void RedactFrom_RedactsEveryTokenFromIndex()
    {
        // A direct AppHost launch has no separator: the leading arguments come from MSBuild
        // RunArguments and the launch profile, and the tail is user-supplied AppHost input.
        string[] args = ["--from-msbuild", "value", "--ApiKey", "sk-live-secret"];
 
        Assert.Equal(
            ["--from-msbuild", "value", "<redacted>", "<redacted>"],
            AppHostArgumentRedactor.RedactFrom(args, 2));
    }
 
    [Fact]
    public void RedactFrom_IndexAtOrPastEnd_ReturnsArgumentsUnchanged()
    {
        string[] args = ["--from-msbuild", "value"];
 
        Assert.Equal(args, AppHostArgumentRedactor.RedactFrom(args, 2));
        Assert.Equal(args, AppHostArgumentRedactor.RedactFrom(args, 5));
    }
 
    [Fact]
    public void RedactFrom_ZeroIndex_RedactsEveryToken()
    {
        Assert.Equal(
            ["<redacted>", "<redacted>"],
            AppHostArgumentRedactor.RedactFrom(["--ApiKey", "sk-live-secret"], 0));
    }
 
    [Fact]
    public void RedactFromToString_JoinsRedactedTokensWithSpaces()
    {
        string[] args = ["--from-msbuild", "value", "--ApiKey", "sk-live-secret"];
        var redacted = AppHostArgumentRedactor.RedactFromToString(args, 2);
 
        Assert.Equal("--from-msbuild value <redacted> <redacted>", redacted);
        Assert.DoesNotContain("sk-live-secret", redacted, StringComparison.Ordinal);
    }
}