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
{
/// Verifies that InvokerExecute logs inside call site with structured properties.
[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");
}
/// Verifies that InvokerExecute does not leak context outside the call site.
[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
{
/// Gets the list of captured log events.
public List Events { get; } = [];
/// Emits a log event by adding it to the captured events list.
/// The log event to emit.
public void Emit(LogEvent logEvent) => Events.Add(logEvent);
}
}