File: TestServices\TestExtensionInteractionService.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.Backchannel;
using Aspire.Cli.Interaction;
using Aspire.Cli.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Spectre.Console;
using Spectre.Console.Rendering;
 
namespace Aspire.Cli.Tests.TestServices;
 
internal sealed class TestExtensionInteractionService(IServiceProvider serviceProvider) : IExtensionInteractionService
{
    public ConsoleOutput Console { get; set; }
    public bool SupportsLinks { get; set; }
    public Action<string>? DisplayErrorCallback { get; set; }
    public Action<string>? DisplaySubtleMessageCallback { get; set; }
    public Action<string>? DisplayConsoleWriteLineMessage { get; set; }
    public Action? LaunchAppHostCallback { get; set; }
    public Action? NotifyAppHostStartupCompletedCallback { get; set; }
    public Action<DashboardUrlsState>? DisplayDashboardUrlsCallback { get; set; }
    public Action<string, string?, bool, DebugSessionOptions?>? StartDebugSessionCallback { get; set; }
    public Action<string, bool, string?>? WriteDebugSessionMessageCallback { get; set; }
    public Action<ExtensionAppHostLogEntry>? WriteAppHostLogEntryCallback { get; set; }
    public Action<string, bool>? ConsoleDisplaySubtleMessageCallback { get; set; }
    public Func<string, bool, bool>? ConfirmCallback { get; set; }
    public Func<string, Func<string, ValidationResult>?, bool, bool, PromptBinding<string?>?, CancellationToken, Task<string>>? PromptForStringCallback { get; set; }
    public Func<string, IReadOnlyList<string>, string>? SelectionCallback { get; set; }
    public Func<IRenderable, Func<Action<IRenderable>, Task>, Task>? DisplayLiveAsyncCallback { get; set; }
    public List<(OutputLineStream Stream, string Line)> DisplayedLines { get; } = [];
    public bool FlushAsyncCalled { get; private set; }
 
    public IExtensionBackchannel Backchannel { get; } = serviceProvider.GetRequiredService<IExtensionBackchannel>();
 
    public Task FlushAsync(CancellationToken cancellationToken = default)
    {
        FlushAsyncCalled = true;
        return Task.CompletedTask;
    }
 
    public Task<T> ShowStatusAsync<T>(string statusText, Func<Task<T>> action, KnownEmoji? emoji = null, bool allowMarkup = false)
    {
        return action();
    }
 
    public Task<T> ShowDynamicStatusAsync<T>(string initialStatusText, Func<Action<string>, Task<T>> action, KnownEmoji? emoji = null)
    {
        return action(_ => { });
    }
 
    public void ShowStatus(string statusText, Action action, KnownEmoji? emoji = null, bool allowMarkup = false)
    {
        action();
    }
 
    public Task<string> PromptForStringAsync(string promptText, Func<string, ValidationResult>? validator = null, bool isSecret = false, bool required = false, PromptBinding<string?>? binding = null, CancellationToken cancellationToken = default)
    {
        if (PromptForStringCallback is not null)
        {
            return PromptForStringCallback(promptText, validator, isSecret, required, binding, cancellationToken);
        }
 
        return Task.FromResult(binding?.DefaultValue ?? string.Empty);
    }
 
    public Task<string> PromptForFilePathAsync(string promptText, Func<string, ValidationResult>? validator = null, bool directory = false, bool required = false, PromptBinding<string?>? binding = null, bool retryOnValidationFailure = false, CancellationToken cancellationToken = default)
    {
        return PromptForStringAsync(promptText, validator, isSecret: false, required, binding, cancellationToken);
    }
 
    public Task<T> PromptForSelectionAsync<T>(string promptText, IEnumerable<T> choices, Func<T, string> choiceFormatter, PromptBinding<string?>? binding = null, bool echoSelected = true, CancellationToken cancellationToken = default) where T : notnull
    {
        var choicesArray = choices.ToArray();
        if (choicesArray.Length == 0)
        {
            throw new EmptyChoicesException($"No items available for selection: {promptText}");
        }
 
        if (SelectionCallback is not null)
        {
            var selected = SelectionCallback(promptText, choicesArray.Select(choiceFormatter).ToArray());
            var matchingChoice = choicesArray.FirstOrDefault(c => string.Equals(choiceFormatter(c), selected, StringComparison.Ordinal));
            if (matchingChoice is not null)
            {
                return Task.FromResult(matchingChoice);
            }
        }
 
        return Task.FromResult(choicesArray.First());
    }
 
    public Task<IReadOnlyList<T>> PromptForSelectionsAsync<T>(string promptText, IEnumerable<T> choices, Func<T, string> choiceFormatter, IEnumerable<T>? preSelected = null, bool optional = false, PromptBinding<string?>? binding = null, bool echoSelected = true, IEnumerable<T>? bindingChoices = null, CancellationToken cancellationToken = default) where T : notnull
    {
        if (!choices.Any())
        {
            throw new EmptyChoicesException($"No items available for selection: {promptText}");
        }
 
        if (preSelected is not null)
        {
            return Task.FromResult<IReadOnlyList<T>>(preSelected.ToList());
        }
 
        return Task.FromResult<IReadOnlyList<T>>(choices.ToList());
    }
 
    public int DisplayIncompatibleVersionError(AppHostIncompatibleException ex, string appHostHostingVersion)
    {
        return 0;
    }
 
    public void DisplayError(string errorMessage, bool allowMarkup = false)
    {
        DisplayErrorCallback?.Invoke(errorMessage);
    }
 
    public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false, ConsoleOutput? consoleOverride = null)
    {
    }
 
    public void DisplaySuccess(string message, bool allowMarkup = false)
    {
    }
 
    public void DisplayDashboardUrls(DashboardUrlsState dashboardUrls)
    {
        DisplayDashboardUrlsCallback?.Invoke(dashboardUrls);
    }
 
    public void NotifyAppHostStartupCompleted()
    {
        NotifyAppHostStartupCompletedCallback?.Invoke();
    }
 
    public void DisplayConsolePlainText(string message)
    {
        DisplayConsoleWriteLineMessage?.Invoke(message);
    }
 
    public Task StartDebugSessionAsync(string workingDirectory, string? projectFile, bool debug, DebugSessionOptions? options = null)
    {
        StartDebugSessionCallback?.Invoke(workingDirectory, projectFile, debug, options);
        return Task.CompletedTask;
    }
 
    public void WriteDebugSessionMessage(string message, bool stdout, string? textStyle)
    {
        WriteDebugSessionMessageCallback?.Invoke(message, stdout, textStyle);
    }
 
    public void WriteAppHostLogEntry(ExtensionAppHostLogEntry entry)
    {
        WriteAppHostLogEntryCallback?.Invoke(entry);
    }
 
    public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines)
    {
        DisplayedLines.AddRange(lines);
    }
 
    public void DisplayCancellationMessage(string? message = null, ConsoleOutput? consoleOverride = null)
    {
    }
 
    public Task<bool> PromptConfirmAsync(string promptText, PromptBinding<bool>? binding = null, CancellationToken cancellationToken = default)
    {
        var defaultValue = binding?.DefaultValue ?? false;
        return Task.FromResult(ConfirmCallback?.Invoke(promptText, defaultValue) ?? true);
    }
 
    public void DisplaySubtleMessage(string message, bool allowMarkup = false)
    {
        DisplaySubtleMessageCallback?.Invoke(message);
    }
 
    public void DisplayEmptyLine()
    {
    }
 
    public void DisplayPlainText(string text)
    {
    }
 
    public void DisplayRawText(string text, ConsoleOutput? consoleOverride = null)
    {
    }
 
    public void DisplayMarkdown(string markdown, ConsoleOutput? consoleOverride = null, int? maxWidth = null)
    {
    }
 
    public void DisplayMarkupLine(string markup)
    {
    }
 
    public void WriteConsoleLog(string message, int? lineNumber = null, string? type = null, bool isErrorMessage = false)
    {
        var output = $"[{(isErrorMessage ? "Error" : type ?? "Info")}] {message} (Line: {lineNumber})";
        DisplayConsoleWriteLineMessage?.Invoke(output);
    }
 
    public Action<string>? DisplayVersionUpdateNotificationCallback { get; set; }
 
    public void DisplayVersionUpdateNotification(string newerVersion, string? updateCommand = null)
    {
        DisplayVersionUpdateNotificationCallback?.Invoke(newerVersion);
    }
 
    public void DisplayRenderable(IRenderable renderable)
    {
    }
 
    public Task DisplayLiveAsync(IRenderable initialRenderable, Func<Action<IRenderable>, Task> callback)
    {
        if (DisplayLiveAsyncCallback is not null)
        {
            return DisplayLiveAsyncCallback(initialRenderable, callback);
        }
 
        return callback(_ => { });
    }
 
    public Action<string>? OpenEditorCallback { get; set; }
 
    public void OpenEditor(string projectPath)
    {
        OpenEditorCallback?.Invoke(projectPath);
    }
 
    public Action<int, string>? RequestAppHostAttachCallback { get; set; }
 
    public Task RequestAppHostAttachAsync(int processId, string projectName)
    {
        RequestAppHostAttachCallback?.Invoke(processId, projectName);
        return Task.CompletedTask;
    }
 
    public Action<LogLevel, string>? LogMessageCallback { get; set; }
 
    public void LogMessage(LogLevel logLevel, string message)
    {
        LogMessageCallback?.Invoke(logLevel, message);
    }
 
    public Task LaunchAppHostAsync(string projectFile, List<string> arguments, List<EnvVar> environment, bool debug)
    {
        LaunchAppHostCallback?.Invoke();
        return Task.CompletedTask;
    }
 
    public void ConsoleDisplaySubtleMessage(string message, bool allowMarkup = false)
    {
        ConsoleDisplaySubtleMessageCallback?.Invoke(message, allowMarkup);
    }
}