File: CreateFailingTestIssue\GitHubCliArgumentTests.cs
Web Access
Project: src\tests\Infrastructure.Tests\Infrastructure.Tests.csproj (Infrastructure.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.TestTools;
using Xunit;
 
namespace Infrastructure.Tests;
 
/// <summary>
/// Guards the <c>gh</c> argument list built for GitHub API calls.
/// </summary>
/// <remarks>
/// The fixture-directory seam used by the other CreateFailingTestIssue tests returns canned content before
/// any process arguments are constructed, so those tests cannot observe this. These tests replace the
/// invoker itself, which is the only place the flag is visible. Both tests stay in one class so xUnit keeps
/// them in the same collection and never runs them concurrently against the static
/// <see cref="GitHubCli.GhInvokerOverride"/>.
/// </remarks>
public class GitHubCliArgumentTests
{
    [Fact]
    public async Task JobLogDownloadAllowsTerminalEscapeSequences()
    {
        var arguments = await CaptureArgumentsAsync(
            () => GitHubActionsApi.DownloadJobLogAsync("microsoft/aspire", 12345, CancellationToken.None));
 
        // CLI end-to-end job logs embed the raw terminal recording, so `gh` refuses to write them to a
        // non-TTY stdout without this flag and fails the whole call.
        Assert.Equal(
            ["api", "-H", "Accept: application/vnd.github+json", "--allow-escape-sequences", "repos/microsoft/aspire/actions/jobs/12345/logs"],
            arguments);
    }
 
    [Fact]
    public async Task OrdinaryApiCallsDoNotAllowTerminalEscapeSequences()
    {
        var arguments = await CaptureArgumentsAsync(
            () => GitHubCli.GetStringAsync("repos/microsoft/aspire/actions/runs/1", CancellationToken.None));
 
        // A JSON payload carrying terminal control characters is worth failing on, so the flag stays off
        // everywhere except the logs endpoint.
        Assert.Equal(
            ["api", "-H", "Accept: application/vnd.github+json", "repos/microsoft/aspire/actions/runs/1"],
            arguments);
    }
 
    private static async Task<IReadOnlyList<string>> CaptureArgumentsAsync(Func<Task<string>> call)
    {
        IReadOnlyList<string> captured = [];
 
        // The fixture seam returns canned content before argument construction, so clear it here to ensure
        // these tests exercise the production call path all the way down to the gh invoker.
        var previousFixtureDirectory = Environment.GetEnvironmentVariable("ASPIRE_FAILING_TEST_ISSUE_FIXTURE_DIR");
        Environment.SetEnvironmentVariable("ASPIRE_FAILING_TEST_ISSUE_FIXTURE_DIR", null);
 
        GitHubCli.GhInvokerOverride = (arguments, _) =>
        {
            captured = arguments;
            return Task.FromResult("{}");
        };
 
        try
        {
            await call();
        }
        finally
        {
            GitHubCli.GhInvokerOverride = null;
            Environment.SetEnvironmentVariable("ASPIRE_FAILING_TEST_ISSUE_FIXTURE_DIR", previousFixtureDirectory);
        }
 
        return captured;
    }
}