using Serilog; using Serilog.Core; using Serilog.Events; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.Resilience; namespace ZB.MOM.WW.OtOpcUa.Core.Tests.Observability; [Trait("Category", "Integration")] public sealed class CapabilityInvokerEnrichmentTests { [Fact] public async Task InvokerExecute_LogsInsideCallSite_CarryStructuredProperties() { var sink = new InMemorySink(); var logger = new LoggerConfiguration() .Enrich.FromLogContext() .WriteTo.Sink(sink) .CreateLogger(); var invoker = new CapabilityInvoker( new DriverResiliencePipelineBuilder(), driverInstanceId: "drv-live", optionsAccessor: () => new DriverResilienceOptions { Tier = DriverTier.A }, driverType: "Modbus"); await invoker.ExecuteAsync( DriverCapability.Read, "plc-1", ct => { logger.Information("inside call site"); return ValueTask.FromResult(42); }, CancellationToken.None); var evt = sink.Events.ShouldHaveSingleItem(); evt.Properties["DriverInstanceId"].ToString().ShouldBe("\"drv-live\""); evt.Properties["DriverType"].ToString().ShouldBe("\"Modbus\""); evt.Properties["CapabilityName"].ToString().ShouldBe("\"Read\""); evt.Properties.ShouldContainKey("CorrelationId"); } [Fact] public async Task InvokerExecute_DoesNotLeak_ContextOutsideCallSite() { var sink = new InMemorySink(); var logger = new LoggerConfiguration() .Enrich.FromLogContext() .WriteTo.Sink(sink) .CreateLogger(); var invoker = new CapabilityInvoker( new DriverResiliencePipelineBuilder(), driverInstanceId: "drv-a", optionsAccessor: () => new DriverResilienceOptions { Tier = DriverTier.A }); await invoker.ExecuteAsync(DriverCapability.Read, "host", _ => ValueTask.FromResult(1), CancellationToken.None); logger.Information("outside"); var outside = sink.Events.ShouldHaveSingleItem(); outside.Properties.ContainsKey("DriverInstanceId").ShouldBeFalse(); } private sealed class InMemorySink : ILogEventSink { public List Events { get; } = []; public void Emit(LogEvent logEvent) => Events.Add(logEvent); } }