| File: Helpers\SecretInputTestHelpers.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 Hex1b.Automation; using Hex1b.Input; using Xunit; namespace Aspire.Cli.EndToEnd.Tests.Helpers; internal static class SecretInputTestHelpers { internal static async Task VerifyInputSourcesAsync( Hex1bTerminalAutomator auto, SequenceCounter counter, TemporaryWorkspace workspace, string appHostOptions) { const string environmentMarker = "environment-secret-not-for-diagnostics"; const string environmentValue = environmentMarker + "\n $literal [value] \"quoted\" "; const string promptedValue = "prompted-secret-not-for-diagnostics"; const string cancelledValue = "cancelled-secret-not-for-diagnostics"; const string key = "Parameters:input-secret"; var setCommand = $"aspire secret set {key}{appHostOptions}"; var valuePath = Path.Combine(workspace.Path, "secret-value.txt"); var sourcePath = Path.Combine(workspace.Path, "secret-source.txt"); var patternsPath = Path.Combine(workspace.Path, "secret-log-patterns.txt"); var containerValuePath = AspireCliShellCommandHelpers.QuoteBashArg(CliE2ETestHelpers.ToContainerPath(valuePath, workspace)); var containerSourcePath = AspireCliShellCommandHelpers.QuoteBashArg(CliE2ETestHelpers.ToContainerPath(sourcePath, workspace)); var containerPatternsPath = AspireCliShellCommandHelpers.QuoteBashArg(CliE2ETestHelpers.ToContainerPath(patternsPath, workspace)); var getCommand = $"aspire secret get {key}{appHostOptions} > {containerValuePath}"; // Keep the value out of shell commands and terminal history as well as CLI arguments. await File.WriteAllTextAsync(sourcePath, environmentValue); await File.WriteAllLinesAsync(patternsPath, [environmentMarker, promptedValue, cancelledValue]); await auto.RunCommandAsync($"export ASPIRE_E2E_SECRET_VALUE=\"$(cat {containerSourcePath})\"; unset ASPIRE_E2E_MISSING_SECRET", counter); await auto.ClearScreenAsync(counter); await auto.RunCommandAsync($"{setCommand} --envvar ASPIRE_E2E_SECRET_VALUE --log-level Trace", counter); Assert.False(auto.CreateSnapshot().ContainsText(environmentMarker)); await AssertStoredValueAsync(environmentValue); await AssertCommandFailsAsync( $"{setCommand} positional-value --envvar ASPIRE_E2E_SECRET_VALUE", "Specify either a positional secret value or --envvar, not both."); await AssertCommandFailsAsync( $"{setCommand} --envvar ASPIRE_E2E_MISSING_SECRET", "Environment variable 'ASPIRE_E2E_MISSING_SECRET' is not set."); await AssertCommandFailsAsync( $"{setCommand} --non-interactive", "Specify a positional secret value or --envvar"); await AssertStoredValueAsync(environmentValue); await auto.RunCommandAsync("export ASPIRE_E2E_SECRET_VALUE=''", counter); await auto.RunCommandAsync($"{setCommand} --envvar ASPIRE_E2E_SECRET_VALUE --non-interactive", counter); await AssertStoredValueAsync(""); await auto.RunCommandAsync($"{setCommand} '' --non-interactive", counter); await AssertStoredValueAsync(""); await StartSecretPromptAsync(); await auto.TypeAsync(promptedValue); await WaitForMaskedValueAsync(promptedValue); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter, TimeSpan.FromSeconds(60)); await AssertStoredValueAsync(promptedValue); await StartSecretPromptAsync(); await auto.TypeAsync(cancelledValue); await WaitForMaskedValueAsync(cancelledValue); var cancelledSequence = counter.Value; await auto.Ctrl().KeyAsync(Hex1bKey.C); await auto.WaitForAnyPromptAsync(counter, TimeSpan.FromSeconds(60)); Assert.True(auto.CreateSnapshot().ContainsText($"[{cancelledSequence} ERR:{CliExitCodes.Cancelled}] $")); await AssertStoredValueAsync(promptedValue); await StartSecretPromptAsync(); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter, TimeSpan.FromSeconds(60)); await AssertStoredValueAsync(""); // grep returns 1 only when no pattern matches; a missing log directory must also fail. await auto.RunCommandAsync( $"grep -RFq -f {containerPatternsPath} \"$HOME/.aspire/logs\"; test $? -eq 1", counter); async Task AssertStoredValueAsync(string expected) { await auto.RunCommandAsync(getCommand, counter, TimeSpan.FromSeconds(60)); Assert.Equal(expected + "\n", await File.ReadAllTextAsync(valuePath)); } async Task AssertCommandFailsAsync(string command, string expectedError) { await auto.ClearScreenAsync(counter); var sequence = counter.Value; await auto.TypeAsync(command); await auto.EnterAsync(); await auto.WaitUntilTextAsync(expectedError, timeout: TimeSpan.FromSeconds(60)); await auto.WaitForAnyPromptAsync(counter, TimeSpan.FromSeconds(60)); Assert.True(auto.CreateSnapshot().ContainsText($"[{sequence} ERR:{CliExitCodes.InvalidCommand}] $")); } async Task StartSecretPromptAsync() { await auto.ClearScreenAsync(counter); await auto.TypeAsync($"{setCommand} --log-level Trace"); await auto.EnterAsync(); await auto.WaitUntilTextAsync("Enter the secret value:", timeout: TimeSpan.FromSeconds(60)); } async Task WaitForMaskedValueAsync(string value) { await auto.WaitUntilAsync( snapshot => snapshot.ContainsText(new string('*', value.Length)), timeout: TimeSpan.FromSeconds(30), description: "waiting for the secret input to be masked"); Assert.False(auto.CreateSnapshot().ContainsText(value)); } } }