File: RecordingLogger.cs
Web Access
Project: src\tests\Aspire.Hosting.Radius.Tests\Aspire.Hosting.Radius.Tests.csproj (Aspire.Hosting.Radius.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Extensions.Logging;
 
namespace Aspire.Hosting.Radius.Tests;
 
/// <summary>
/// Captures every log entry the publisher writes, so tests can assert on diagnostics the publisher
/// emits instead of failing (warnings about omitted variables, replaced credentials, and databases
/// the recipe will not create).
/// </summary>
internal sealed class RecordingLogger : ILogger
{
    private readonly List<(LogLevel Level, string Message)> _entries = [];
 
    public IReadOnlyList<(LogLevel Level, string Message)> Entries => _entries;
 
    public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
 
    public bool IsEnabled(LogLevel logLevel) => true;
 
    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
        => _entries.Add((logLevel, formatter(state, exception)));
 
    /// <summary>
    /// Returns the messages logged at <paramref name="level"/> that contain every fragment in
    /// <paramref name="fragments"/>. Matching on fragments rather than the whole message keeps the
    /// assertion readable without pinning wording that is free to change.
    /// </summary>
    public IReadOnlyList<string> Matching(LogLevel level, params string[] fragments) =>
        _entries
            .Where(e => e.Level == level && fragments.All(f => e.Message.Contains(f, StringComparison.Ordinal)))
            .Select(e => e.Message)
            .ToList();
}