File: Certificates\CertificateServiceTests.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 System.Runtime.InteropServices;
using Aspire.Cli.Certificates;
using Aspire.Cli.DotNet;
using Aspire.Cli.Tests.TestServices;
using Aspire.Cli.Tests.Utils;
using Microsoft.Extensions.DependencyInjection;
 
namespace Aspire.Cli.Tests.Certificates;
 
public class CertificateServiceTests(ITestOutputHelper outputHelper)
{
    [Fact]
    public async Task EnsureCertificatesTrustedAsync_WithFullyTrustedCert_ReturnsEmptyEnvVars()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DotNetCliRunnerFactory = sp =>
            {
                var runner = new TestDotNetCliRunner();
                runner.CheckHttpCertificateMachineReadableAsyncCallback = (_, _) =>
                {
                    return (0, new CertificateTrustResult
                    {
                        HasCertificates = true,
                        TrustLevel = DevCertTrustLevel.Full,
                        Certificates = [new DevCertInfo { Version = 5, TrustLevel = DevCertTrustLevel.Full, IsHttpsDevelopmentCertificate = true, ValidityNotBefore = DateTimeOffset.Now.AddDays(-1), ValidityNotAfter = DateTimeOffset.Now.AddDays(365) }]
                    });
                };
                return runner;
            };
        });
 
        var sp = services.BuildServiceProvider();
        var cs = sp.GetRequiredService<ICertificateService>();
        var runner = sp.GetRequiredService<IDotNetCliRunner>();
 
        var result = await cs.EnsureCertificatesTrustedAsync(runner, TestContext.Current.CancellationToken).WaitAsync(CliTestConstants.DefaultTimeout);
 
        Assert.NotNull(result);
        Assert.Empty(result.EnvironmentVariables);
    }
 
    [Fact]
    public async Task EnsureCertificatesTrustedAsync_WithNotTrustedCert_RunsTrustOperation()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var trustCalled = false;
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DotNetCliRunnerFactory = sp =>
            {
                var runner = new TestDotNetCliRunner();
                var callCount = 0;
                runner.CheckHttpCertificateMachineReadableAsyncCallback = (_, _) =>
                {
                    callCount++;
                    // First call returns not trusted, second call (after trust) returns fully trusted
                    if (callCount == 1)
                    {
                        return (0, new CertificateTrustResult
                        {
                            HasCertificates = true,
                            TrustLevel = DevCertTrustLevel.None,
                            Certificates = [new DevCertInfo { Version = 5, TrustLevel = DevCertTrustLevel.None, IsHttpsDevelopmentCertificate = true, ValidityNotBefore = DateTimeOffset.Now.AddDays(-1), ValidityNotAfter = DateTimeOffset.Now.AddDays(365) }]
                        });
                    }
                    return (0, new CertificateTrustResult
                    {
                        HasCertificates = true,
                        TrustLevel = DevCertTrustLevel.Full,
                        Certificates = [new DevCertInfo { Version = 5, TrustLevel = DevCertTrustLevel.Full, IsHttpsDevelopmentCertificate = true, ValidityNotBefore = DateTimeOffset.Now.AddDays(-1), ValidityNotAfter = DateTimeOffset.Now.AddDays(365) }]
                    });
                };
                runner.TrustHttpCertificateAsyncCallback = (_, _) =>
                {
                    trustCalled = true;
                    return 0;
                };
                return runner;
            };
        });
 
        var sp = services.BuildServiceProvider();
        var cs = sp.GetRequiredService<ICertificateService>();
        var runner = sp.GetRequiredService<IDotNetCliRunner>();
 
        var result = await cs.EnsureCertificatesTrustedAsync(runner, TestContext.Current.CancellationToken).WaitAsync(CliTestConstants.DefaultTimeout);
 
        Assert.True(trustCalled);
        Assert.NotNull(result);
    }
 
    [Fact]
    public async Task EnsureCertificatesTrustedAsync_WithPartiallyTrustedCert_SetsSslCertDirOnLinux()
    {
        // Skip this test on non-Linux platforms
        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            return;
        }
 
        using var workspace = TemporaryWorkspace.Create(outputHelper);
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DotNetCliRunnerFactory = sp =>
            {
                var runner = new TestDotNetCliRunner();
                runner.CheckHttpCertificateMachineReadableAsyncCallback = (_, _) =>
                {
                    return (0, new CertificateTrustResult
                    {
                        HasCertificates = true,
                        TrustLevel = DevCertTrustLevel.Partial,
                        Certificates = [new DevCertInfo { Version = 5, TrustLevel = DevCertTrustLevel.Partial, IsHttpsDevelopmentCertificate = true, ValidityNotBefore = DateTimeOffset.Now.AddDays(-1), ValidityNotAfter = DateTimeOffset.Now.AddDays(365) }]
                    });
                };
                return runner;
            };
        });
 
        var sp = services.BuildServiceProvider();
        var cs = sp.GetRequiredService<ICertificateService>();
        var runner = sp.GetRequiredService<IDotNetCliRunner>();
 
        var result = await cs.EnsureCertificatesTrustedAsync(runner, TestContext.Current.CancellationToken).WaitAsync(CliTestConstants.DefaultTimeout);
 
        Assert.NotNull(result);
        Assert.True(result.EnvironmentVariables.ContainsKey("SSL_CERT_DIR"));
        Assert.Contains(".aspnet/dev-certs/trust", result.EnvironmentVariables["SSL_CERT_DIR"]);
    }
 
    [Fact]
    public async Task EnsureCertificatesTrustedAsync_WithNoCertificates_RunsTrustOperation()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var trustCalled = false;
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DotNetCliRunnerFactory = sp =>
            {
                var runner = new TestDotNetCliRunner();
                var callCount = 0;
                runner.CheckHttpCertificateMachineReadableAsyncCallback = (_, _) =>
                {
                    callCount++;
                    // First call returns no certificates, second call (after trust) returns fully trusted
                    if (callCount == 1)
                    {
                        return (0, new CertificateTrustResult
                        {
                            HasCertificates = false,
                            TrustLevel = null,
                            Certificates = []
                        });
                    }
                    return (0, new CertificateTrustResult
                    {
                        HasCertificates = true,
                        TrustLevel = DevCertTrustLevel.Full,
                        Certificates = [new DevCertInfo { Version = 5, TrustLevel = DevCertTrustLevel.Full, IsHttpsDevelopmentCertificate = true, ValidityNotBefore = DateTimeOffset.Now.AddDays(-1), ValidityNotAfter = DateTimeOffset.Now.AddDays(365) }]
                    });
                };
                runner.TrustHttpCertificateAsyncCallback = (_, _) =>
                {
                    trustCalled = true;
                    return 0;
                };
                return runner;
            };
        });
 
        var sp = services.BuildServiceProvider();
        var cs = sp.GetRequiredService<ICertificateService>();
        var runner = sp.GetRequiredService<IDotNetCliRunner>();
 
        var result = await cs.EnsureCertificatesTrustedAsync(runner, TestContext.Current.CancellationToken).WaitAsync(CliTestConstants.DefaultTimeout);
 
        Assert.True(trustCalled);
        Assert.NotNull(result);
    }
 
    [Fact]
    public async Task EnsureCertificatesTrustedAsync_TrustOperationFails_DisplaysWarning()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.DotNetCliRunnerFactory = sp =>
            {
                var runner = new TestDotNetCliRunner();
                runner.CheckHttpCertificateMachineReadableAsyncCallback = (_, _) =>
                {
                    return (0, new CertificateTrustResult
                    {
                        HasCertificates = true,
                        TrustLevel = DevCertTrustLevel.None,
                        Certificates = [new DevCertInfo { Version = 5, TrustLevel = DevCertTrustLevel.None, IsHttpsDevelopmentCertificate = true, ValidityNotBefore = DateTimeOffset.Now.AddDays(-1), ValidityNotAfter = DateTimeOffset.Now.AddDays(365) }]
                    });
                };
                runner.TrustHttpCertificateAsyncCallback = (options, _) =>
                {
                    Assert.NotNull(options.StandardErrorCallback);
                    options.StandardErrorCallback!.Invoke("There was an error trusting the HTTPS developer certificate. It will be trusted by some clients but not by others.");
                    return 4;
                };
                return runner;
            };
        });
 
        var sp = services.BuildServiceProvider();
        var cs = sp.GetRequiredService<ICertificateService>();
        var runner = sp.GetRequiredService<IDotNetCliRunner>();
 
        // If this does not throw then the code is behaving correctly.
        var result = await cs.EnsureCertificatesTrustedAsync(runner, TestContext.Current.CancellationToken).WaitAsync(CliTestConstants.DefaultTimeout);
        Assert.NotNull(result);
    }
}