diff --git a/src/Aspire.Cli/Certificates/CertificateService.cs b/src/Aspire.Cli/Certificates/CertificateService.cs index 368bf57079f..9dca1f534ae 100644 --- a/src/Aspire.Cli/Certificates/CertificateService.cs +++ b/src/Aspire.Cli/Certificates/CertificateService.cs @@ -42,6 +42,7 @@ public async Task EnsureCertificatesTrustedAsync(IDotNetCliRunner runner, Cancel StandardOutputCallback = ensureCertificateCollector.AppendOutput, StandardErrorCallback = ensureCertificateCollector.AppendError, }; + var trustExitCode = await interactionService.ShowStatusAsync( ":locked_with_key: Trusting certificates...", () => runner.TrustHttpCertificateAsync( @@ -51,10 +52,12 @@ public async Task EnsureCertificatesTrustedAsync(IDotNetCliRunner runner, Cancel if (trustExitCode != 0) { interactionService.DisplayLines(ensureCertificateCollector.GetLines()); - throw new CertificateServiceException($"Failed to trust certificates, trust command failed with exit code: {trustExitCode}"); + interactionService.DisplayMessage("warning", $"Developer certificates may not be fully trusted (trust exit code was: {trustExitCode})"); } } } + + internal const string DevCertsPartialTrustMessage = "There was an error trusting the HTTPS developer certificate. It will be trusted by some clients but not by others."; } public sealed class CertificateServiceException(string message) : Exception(message) diff --git a/tests/Aspire.Cli.Tests/Certificates/CertificateServiceTests.cs b/tests/Aspire.Cli.Tests/Certificates/CertificateServiceTests.cs new file mode 100644 index 00000000000..7aa4b25f495 --- /dev/null +++ b/tests/Aspire.Cli.Tests/Certificates/CertificateServiceTests.cs @@ -0,0 +1,40 @@ +// 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.Certificates; +using Aspire.Cli.Tests.TestServices; +using Aspire.Cli.Tests.Utils; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Aspire.Cli.Tests.Certificates; + +public class CertificateServiceTests(ITestOutputHelper outputHelper) +{ + [Fact] + public async Task EnsureCertificatesTrustedAsyncSucceedsOnNonZeroExitCode() + { + var services = CliTestHelper.CreateServiceCollection(outputHelper, options => + { + options.DotNetCliRunnerFactory = sp => + { + var runner = new TestDotNetCliRunner(); + runner.CheckHttpCertificateAsyncCallback = (_, _) => 1; + runner.TrustHttpCertificateAsyncCallback = (options, _) => + { + Assert.NotNull(options.StandardErrorCallback); + options.StandardErrorCallback!.Invoke(CertificateService.DevCertsPartialTrustMessage); + return 4; + }; + return runner; + }; + }); + + var sp = services.BuildServiceProvider(); + var cs = sp.GetRequiredService(); + var runner = sp.GetRequiredService(); + + // If this does not throw then the code is behaving correctly. + await cs.EnsureCertificatesTrustedAsync(runner, TestContext.Current.CancellationToken).WaitAsync(CliTestConstants.DefaultTimeout); + } +} \ No newline at end of file