namespace HrynCo.Common.Tests; using System.Collections.Concurrent; using HrynCo.Common; using FluentAssertions; using Serilog; using Serilog.Core; using Serilog.Events; using Xunit; public sealed class ProfilerTests { [Fact] public async Task MeasureExecutionAsync_ShouldReturnResultAndWriteStartAndEndEvents() { var sink = new CollectingSink(); ILogger logger = new LoggerConfiguration() .MinimumLevel.Verbose() .WriteTo.Sink(sink) .CreateLogger(); var profiler = new Profiler(logger); int result = await profiler.MeasureExecutionAsync(async () => { await Task.Delay(1); return 42; }, "LoadItems"); result.Should().Be(42); sink.Events.Count.Should().BeGreaterThan(1); sink.Events.Should().ContainSingle(e => e.Level == LogEventLevel.Information && e.MessageTemplate.Text.Contains("Start", StringComparison.Ordinal) && e.Properties["BlockName"].ToString().Contains("LoadItems", StringComparison.Ordinal)); sink.Events.Should().ContainSingle(e => e.Level == LogEventLevel.Information && e.MessageTemplate.Text.Contains("End", StringComparison.Ordinal) && e.Properties["Measurements"].ToString().Contains("True", StringComparison.Ordinal)); } [Fact] public async Task MeasureExecutionAsync_ActionOverload_ShouldInvokeAction() { var sink = new CollectingSink(); ILogger logger = new LoggerConfiguration() .MinimumLevel.Verbose() .WriteTo.Sink(sink) .CreateLogger(); var profiler = new Profiler(logger); bool invoked = false; await profiler.MeasureExecutionAsync(async () => { invoked = true; await Task.CompletedTask; }, "ActionBlock"); invoked.Should().BeTrue(); } [Fact] public async Task MeasureExecutionAsync_ShouldLogErrorAndRethrow() { var sink = new CollectingSink(); ILogger logger = new LoggerConfiguration() .MinimumLevel.Verbose() .WriteTo.Sink(sink) .CreateLogger(); var profiler = new Profiler(logger); Func act = async () => { await profiler.MeasureExecutionAsync(() => throw new InvalidOperationException("boom"), "FailBlock"); }; await act.Should().ThrowAsync(); sink.Events.Should().Contain(e => e.Level == LogEventLevel.Error && e.MessageTemplate.Text.Contains("An error occurred", StringComparison.Ordinal)); } private sealed class CollectingSink : ILogEventSink { public ConcurrentQueue Events { get; } = new(); public void Emit(LogEvent logEvent) { Events.Enqueue(logEvent); } } }