feat(scripting): evaluators log through root script logger → script-log page (F8)
This commit is contained in:
+57
-7
@@ -1,6 +1,10 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Logging;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Scripting;
|
||||
using ZB.MOM.WW.OtOpcUa.Host.Engines;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests;
|
||||
@@ -13,11 +17,25 @@ namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests;
|
||||
/// </summary>
|
||||
public sealed class RoslynScriptedAlarmEvaluatorTests
|
||||
{
|
||||
/// <summary>Captures published <see cref="ScriptLogEntry"/> records for assertion.</summary>
|
||||
private sealed class FakePublisher : IScriptLogPublisher
|
||||
{
|
||||
/// <summary>Gets the entries published so far.</summary>
|
||||
public List<ScriptLogEntry> Published { get; } = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Publish(ScriptLogEntry entry) => Published.Add(entry);
|
||||
}
|
||||
|
||||
/// <summary>Builds a no-op <see cref="ScriptRootLogger"/> for tests that don't assert on logging.</summary>
|
||||
private static ScriptRootLogger NoOpScriptRoot() =>
|
||||
new(new LoggerConfiguration().CreateLogger());
|
||||
|
||||
/// <summary>Verifies evaluation of predicate returning true reports Active.</summary>
|
||||
[Fact]
|
||||
public void Evaluate_predicate_returning_true_reports_Active()
|
||||
{
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance, NoOpScriptRoot());
|
||||
|
||||
var result = sut.Evaluate(
|
||||
alarmId: "alarm-hi",
|
||||
@@ -32,7 +50,7 @@ public sealed class RoslynScriptedAlarmEvaluatorTests
|
||||
[Fact]
|
||||
public void Evaluate_predicate_returning_false_reports_Inactive()
|
||||
{
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance, NoOpScriptRoot());
|
||||
|
||||
var result = sut.Evaluate(
|
||||
alarmId: "alarm-hi",
|
||||
@@ -47,7 +65,7 @@ public sealed class RoslynScriptedAlarmEvaluatorTests
|
||||
[Fact]
|
||||
public void Evaluate_caches_compiled_predicate_across_calls()
|
||||
{
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance, NoOpScriptRoot());
|
||||
const string predicate = "return (bool)ctx.GetTag(\"door_open\").Value;";
|
||||
|
||||
var first = sut.Evaluate("alarm-door", predicate, new Dictionary<string, object?> { ["door_open"] = true });
|
||||
@@ -61,7 +79,7 @@ public sealed class RoslynScriptedAlarmEvaluatorTests
|
||||
[Fact]
|
||||
public void Evaluate_compile_error_returns_Failure()
|
||||
{
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance, NoOpScriptRoot());
|
||||
|
||||
var result = sut.Evaluate("alarm-bad", "this isn't C#;", new Dictionary<string, object?>());
|
||||
|
||||
@@ -73,7 +91,7 @@ public sealed class RoslynScriptedAlarmEvaluatorTests
|
||||
[Fact]
|
||||
public void Evaluate_predicate_writing_virtual_tag_returns_Failure()
|
||||
{
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance, NoOpScriptRoot());
|
||||
|
||||
// AlarmPredicateContext.SetVirtualTag throws — wrapper catches + reports as Failure.
|
||||
var result = sut.Evaluate(
|
||||
@@ -89,7 +107,7 @@ public sealed class RoslynScriptedAlarmEvaluatorTests
|
||||
[Fact]
|
||||
public void Evaluate_empty_predicate_returns_Failure()
|
||||
{
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance, NoOpScriptRoot());
|
||||
|
||||
sut.Evaluate("alarm-empty", "", new Dictionary<string, object?>()).Success.ShouldBeFalse();
|
||||
}
|
||||
@@ -98,7 +116,7 @@ public sealed class RoslynScriptedAlarmEvaluatorTests
|
||||
[Fact]
|
||||
public void Evaluate_after_dispose_returns_Failure()
|
||||
{
|
||||
var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance);
|
||||
var sut = new RoslynScriptedAlarmEvaluator(NullLogger<RoslynScriptedAlarmEvaluator>.Instance, NoOpScriptRoot());
|
||||
sut.Dispose();
|
||||
|
||||
var result = sut.Evaluate("alarm", "return true;", new Dictionary<string, object?>());
|
||||
@@ -106,4 +124,36 @@ public sealed class RoslynScriptedAlarmEvaluatorTests
|
||||
result.Success.ShouldBeFalse();
|
||||
result.Reason!.ShouldContain("disposed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A predicate's <c>ctx.Logger.Warning(...)</c> call flows through the injected root script
|
||||
/// logger and out the <see cref="ScriptLogTopicSink"/>, producing one
|
||||
/// <see cref="ScriptLogEntry"/> carrying the message, the bound <c>AlarmId</c>, and the
|
||||
/// Warning level.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Script_logger_call_publishes_entry_with_bound_alarm_identity()
|
||||
{
|
||||
var publisher = new FakePublisher();
|
||||
var root = new LoggerConfiguration()
|
||||
.MinimumLevel.Verbose()
|
||||
.WriteTo.Sink(new ScriptLogTopicSink(publisher, LogEventLevel.Information))
|
||||
.CreateLogger();
|
||||
using var sut = new RoslynScriptedAlarmEvaluator(
|
||||
NullLogger<RoslynScriptedAlarmEvaluator>.Instance, new ScriptRootLogger(root));
|
||||
|
||||
var result = sut.Evaluate(
|
||||
alarmId: "alarm-log",
|
||||
predicate: "ctx.Logger.Warning(\"alarm log\"); return true;",
|
||||
dependencies: new Dictionary<string, object?>());
|
||||
|
||||
result.Success.ShouldBeTrue(result.Reason);
|
||||
result.Active.ShouldBeTrue();
|
||||
publisher.Published.Count.ShouldBe(1);
|
||||
var entry = publisher.Published[0];
|
||||
entry.Message.ShouldBe("alarm log");
|
||||
entry.AlarmId.ShouldBe("alarm-log");
|
||||
entry.ScriptId.ShouldBe("alarm-log");
|
||||
entry.Level.ShouldBe("Warning");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user