File: tests\Shared\TestPipelineActivityReporter.cs
Web Access
Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#pragma warning disable ASPIREPIPELINES001
 
using Aspire.Hosting.Pipelines;
using Microsoft.Extensions.Logging;
using Xunit;
 
namespace Aspire.Hosting.Utils;
 
/// <summary>
/// A test implementation of <see cref="IPipelineActivityReporter"/> that captures activity for test assertions.
/// </summary>
internal sealed class TestPipelineActivityReporter : IPipelineActivityReporter
{
    private readonly ITestOutputHelper _testOutputHelper;
 
    /// <summary>
    /// Initializes a new instance of the <see cref="TestPipelineActivityReporter"/> class.
    /// </summary>
    /// <param name="testOutputHelper">The test output helper to write to.</param>
    public TestPipelineActivityReporter(ITestOutputHelper testOutputHelper)
    {
        _testOutputHelper = testOutputHelper ?? throw new ArgumentNullException(nameof(testOutputHelper));
    }
 
    /// <summary>
    /// Gets a list of all steps that have been created.
    /// </summary>
    public List<string> CreatedSteps { get; } = [];
 
    /// <summary>
    /// Gets a list of all tasks that have been created.
    /// </summary>
    public List<(string StepTitle, string TaskStatusText)> CreatedTasks { get; } = [];
 
    /// <summary>
    /// Gets a list of all steps that have been completed.
    /// </summary>
    public List<(string StepTitle, string CompletionText, CompletionState CompletionState)> CompletedSteps { get; } = [];
 
    /// <summary>
    /// Gets a list of all tasks that have been completed.
    /// </summary>
    public List<(string TaskStatusText, string? CompletionMessage, CompletionState CompletionState)> CompletedTasks { get; } = [];
 
    /// <summary>
    /// Gets a list of all tasks that have been updated.
    /// </summary>
    public List<(string TaskStatusText, string StatusText)> UpdatedTasks { get; } = [];
 
    /// <summary>
    /// Gets a list of all log messages that have been logged.
    /// </summary>
    public List<(string StepTitle, LogLevel LogLevel, string Message)> LoggedMessages { get; } = [];
 
    /// <summary>
    /// Gets a value indicating whether <see cref="CompletePublishAsync"/> has been called.
    /// </summary>
    public bool CompletePublishCalled { get; private set; }
 
    /// <summary>
    /// Gets the completion message passed to <see cref="CompletePublishAsync"/>.
    /// </summary>
    public string? CompletionMessage { get; private set; }
 
    /// <summary>
    /// Gets the completion state passed to <see cref="CompletePublishAsync"/>.
    /// </summary>
    public CompletionState? ResultCompletionState { get; private set; }
 
    /// <summary>
    /// Clears all captured state to allow reuse between pipeline runs.
    /// </summary>
    public void Clear()
    {
        lock (CreatedSteps)
        {
            CreatedSteps.Clear();
        }
        lock (CreatedTasks)
        {
            CreatedTasks.Clear();
        }
        lock (CompletedSteps)
        {
            CompletedSteps.Clear();
        }
        lock (CompletedTasks)
        {
            CompletedTasks.Clear();
        }
        lock (UpdatedTasks)
        {
            UpdatedTasks.Clear();
        }
        lock (LoggedMessages)
        {
            LoggedMessages.Clear();
        }
        CompletePublishCalled = false;
        CompletionMessage = null;
        ResultCompletionState = null;
    }
 
    /// <inheritdoc />
    public Task CompletePublishAsync(string? completionMessage = null, CompletionState? completionState = null, CancellationToken cancellationToken = default)
    {
        CompletePublishCalled = true;
        CompletionMessage = completionMessage;
        ResultCompletionState = completionState;
        _testOutputHelper.WriteLine($"[CompletePublish] {completionMessage} (State: {completionState})");
 
        return Task.CompletedTask;
    }
 
    /// <inheritdoc />
    public Task<IReportingStep> CreateStepAsync(string title, CancellationToken cancellationToken = default)
    {
        lock (CreatedSteps)
        {
            CreatedSteps.Add(title);
        }
        _testOutputHelper.WriteLine($"[CreateStep] {title}");
 
        return Task.FromResult<IReportingStep>(new TestReportingStep(this, title, _testOutputHelper));
    }
 
    private sealed class TestReportingStep : IReportingStep
    {
        private readonly TestPipelineActivityReporter _reporter;
        private readonly string _title;
        private readonly ITestOutputHelper _testOutputHelper;
 
        public TestReportingStep(TestPipelineActivityReporter reporter, string title, ITestOutputHelper testOutputHelper)
        {
            _reporter = reporter;
            _title = title;
            _testOutputHelper = testOutputHelper;
        }
 
        public ValueTask DisposeAsync() => ValueTask.CompletedTask;
 
        public Task CompleteAsync(string completionText, CompletionState completionState = CompletionState.Completed, CancellationToken cancellationToken = default)
        {
            lock (_reporter.CompletedSteps)
            {
                _reporter.CompletedSteps.Add((_title, completionText, completionState));
            }
 
            _testOutputHelper.WriteLine($"  [CompleteStep:{_title}] {completionText} (State: {completionState})");
 
            return Task.CompletedTask;
        }
 
        public Task<IReportingTask> CreateTaskAsync(string statusText, CancellationToken cancellationToken = default)
        {
            lock (_reporter.CreatedTasks)
            {
                _reporter.CreatedTasks.Add((_title, statusText));
            }
            _testOutputHelper.WriteLine($"    [CreateTask:{_title}] {statusText}");
 
            return Task.FromResult<IReportingTask>(new TestReportingTask(_reporter, statusText, _testOutputHelper));
        }
 
        public void Log(LogLevel logLevel, string message, bool enableMarkdown)
        {
            lock (_reporter.LoggedMessages)
            {
                _reporter.LoggedMessages.Add((_title, logLevel, message));
            }
            _testOutputHelper.WriteLine($"    [{logLevel}:{_title}] {message}");
        }
    }
 
    private sealed class TestReportingTask : IReportingTask
    {
        private readonly TestPipelineActivityReporter _reporter;
        private readonly string _initialStatusText;
        private readonly ITestOutputHelper _testOutputHelper;
 
        public TestReportingTask(TestPipelineActivityReporter reporter, string initialStatusText, ITestOutputHelper testOutputHelper)
        {
            _reporter = reporter;
            _initialStatusText = initialStatusText;
            _testOutputHelper = testOutputHelper;
        }
 
        public ValueTask DisposeAsync() => ValueTask.CompletedTask;
 
        public Task CompleteAsync(string? completionMessage = null, CompletionState completionState = CompletionState.Completed, CancellationToken cancellationToken = default)
        {
            lock (_reporter.CompletedTasks)
            {
                _reporter.CompletedTasks.Add((_initialStatusText, completionMessage, completionState));
            }
            _testOutputHelper.WriteLine($"      [CompleteTask:{_initialStatusText}] {completionMessage} (State: {completionState})");
 
            return Task.CompletedTask;
        }
 
        public Task UpdateAsync(string statusText, CancellationToken cancellationToken = default)
        {
            lock (_reporter.UpdatedTasks)
            {
                _reporter.UpdatedTasks.Add((_initialStatusText, statusText));
            }
            _testOutputHelper.WriteLine($"      [UpdateTask:{_initialStatusText}] {statusText}");
 
            return Task.CompletedTask;
        }
    }
}