using Akka.Actor;
using Akka.Event;
using Akka.TestKit.Xunit2;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection;
using ZB.MOM.WW.ScadaBridge.Commons.Observability;
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Actors;
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Logging;
using ZB.MOM.WW.ScadaBridge.HealthMonitoring;
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests.Actors;
///
/// ScadaBridge#120: the DCL actors' own Akka _log lines record the execution id of the
/// message being served, so the host's Serilog logger actor can tag them although it writes them
/// on another thread.
///
public class ExecutionAwareLoggingTests : TestKit
{
/// Debug level so the DCL's write-path Debug line is published.
public ExecutionAwareLoggingTests() : base("akka.loglevel = DEBUG")
{
}
private sealed record LogUnder(Guid? Id);
private sealed class Probe : ReceiveActor
{
private readonly ILoggingAdapter _log = ExecutionAwareLogging.GetLogger(Context);
public Probe()
{
Receive(m =>
{
var id = m.Id;
using (ExecutionLogContext.Push(id))
_log.Info("probe {0}", id?.ToString() ?? "none");
});
Receive(_ => _log.Debug("debug {0}", "x"));
}
}
/// A line logged inside an execution's push is published wrapped with that id; its text is unchanged.
[Fact]
public void LineInsideAPush_IsTaggedWithTheExecutionId_TextUnchanged()
{
var events = CreateTestProbe();
Sys.EventStream.Subscribe(events.Ref, typeof(Info));
var id = Guid.NewGuid();
Sys.ActorOf(Props.Create(() => new Probe())).Tell(new LogUnder(id));
var info = events.FishForMessage(i => i.Message.ToString()!.StartsWith("probe", StringComparison.Ordinal));
var tagged = Assert.IsType(info.Message);
Assert.Equal(id, tagged.ExecutionId);
Assert.Equal($"probe {id}", info.Message.ToString());
Assert.IsAssignableFrom(tagged.Message);
}
/// With no execution current, the line is published exactly as Context.GetLogger() would publish it.
[Fact]
public void LineOutsideAPush_IsPublishedUnwrapped()
{
var events = CreateTestProbe();
Sys.EventStream.Subscribe(events.Ref, typeof(Info));
Sys.ActorOf(Props.Create(() => new Probe())).Tell(new LogUnder(null));
var info = events.FishForMessage(i => i.Message.ToString()!.StartsWith("probe", StringComparison.Ordinal));
Assert.IsAssignableFrom(info.Message);
Assert.Equal("probe none", info.Message.ToString());
}
/// The adapter honours akka.loglevel: a Debug line is not published once the bus is at Info.
[Fact]
public void DisabledLevel_PublishesNothing()
{
Sys.EventStream.SetLogLevel(Akka.Event.LogLevel.InfoLevel);
var events = CreateTestProbe();
Sys.EventStream.Subscribe(events.Ref, typeof(Debug));
Sys.ActorOf(Props.Create(() => new Probe())).Tell("debug");
events.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
}
///
/// End to end on the DCL: the DataConnectionActor's own write-path Debug line, logged while it
/// serves a correlated WriteTagRequest, is tagged with the request's execution id; an
/// uncorrelated write's line is not (control).
///
/// A task that represents the asynchronous operation.
[Fact]
public async Task DataConnectionActor_WritePathLine_IsTagged_UncorrelatedIsNot()
{
var adapter = Substitute.For();
adapter.ConnectAsync(Arg.Any>(), Arg.Any())
.Returns(Task.CompletedTask);
adapter.WriteAsync(Arg.Any(), Arg.Any