using Microsoft.Extensions.Logging.Abstractions; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Host.Engines; namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests; /// /// F9b — verifies compiles alarm predicates, /// returns the bool result on success, surfaces compile/runtime errors as Failure (so the /// actor preserves prior state), and rejects predicates that try to ctx.SetVirtualTag (the /// AlarmPredicateContext throws on writes — predicates must stay pure). /// public sealed class RoslynScriptedAlarmEvaluatorTests { [Fact] public void Evaluate_predicate_returning_true_reports_Active() { using var sut = new RoslynScriptedAlarmEvaluator(NullLogger.Instance); var result = sut.Evaluate( alarmId: "alarm-hi", predicate: "return (int)ctx.GetTag(\"temp\").Value > 100;", dependencies: new Dictionary { ["temp"] = 150 }); result.Success.ShouldBeTrue(result.Reason); result.Active.ShouldBeTrue(); } [Fact] public void Evaluate_predicate_returning_false_reports_Inactive() { using var sut = new RoslynScriptedAlarmEvaluator(NullLogger.Instance); var result = sut.Evaluate( alarmId: "alarm-hi", predicate: "return (int)ctx.GetTag(\"temp\").Value > 100;", dependencies: new Dictionary { ["temp"] = 50 }); result.Success.ShouldBeTrue(result.Reason); result.Active.ShouldBeFalse(); } [Fact] public void Evaluate_caches_compiled_predicate_across_calls() { using var sut = new RoslynScriptedAlarmEvaluator(NullLogger.Instance); const string predicate = "return (bool)ctx.GetTag(\"door_open\").Value;"; var first = sut.Evaluate("alarm-door", predicate, new Dictionary { ["door_open"] = true }); var second = sut.Evaluate("alarm-door", predicate, new Dictionary { ["door_open"] = false }); first.Active.ShouldBeTrue(); second.Active.ShouldBeFalse(); } [Fact] public void Evaluate_compile_error_returns_Failure() { using var sut = new RoslynScriptedAlarmEvaluator(NullLogger.Instance); var result = sut.Evaluate("alarm-bad", "this isn't C#;", new Dictionary()); result.Success.ShouldBeFalse(); result.Reason!.ShouldContain("compile"); } [Fact] public void Evaluate_predicate_writing_virtual_tag_returns_Failure() { using var sut = new RoslynScriptedAlarmEvaluator(NullLogger.Instance); // AlarmPredicateContext.SetVirtualTag throws — wrapper catches + reports as Failure. var result = sut.Evaluate( alarmId: "alarm-bad-write", predicate: "ctx.SetVirtualTag(\"x\", 1); return true;", dependencies: new Dictionary()); result.Success.ShouldBeFalse(); result.Reason!.ShouldContain("threw"); } [Fact] public void Evaluate_empty_predicate_returns_Failure() { using var sut = new RoslynScriptedAlarmEvaluator(NullLogger.Instance); sut.Evaluate("alarm-empty", "", new Dictionary()).Success.ShouldBeFalse(); } [Fact] public void Evaluate_after_dispose_returns_Failure() { var sut = new RoslynScriptedAlarmEvaluator(NullLogger.Instance); sut.Dispose(); var result = sut.Evaluate("alarm", "return true;", new Dictionary()); result.Success.ShouldBeFalse(); result.Reason!.ShouldContain("disposed"); } }