using System.Text.Json; using Microsoft.Extensions.Logging.Abstractions; using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening; using ZB.MOM.WW.ScadaBridge.SiteRuntime.Deployment; using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts; namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Deployment; /// /// Site Runtime S3: the pure deploy-time compile validator. It mirrors the /// compile work /// does at runtime (scripts, on-trigger scripts, Expression triggers) so the /// Deployment Manager can reject a non-compiling deployment before any actor is /// created — WITHOUT touching actor state (safe on a Task.Run thread). /// public class DeployCompileValidatorTests { private static DeployCompileValidator NewValidator() => new(new ScriptCompilationService(NullLogger.Instance)); private static string ConfigWith(string scriptCode) => JsonSerializer.Serialize(new FlattenedConfiguration { Scripts = new List { new() { CanonicalName = "S1", Code = scriptCode, TriggerType = "Call" } }, Attributes = new List(), Alarms = new List(), NativeAlarmSources = new List() }); [Fact] public void BrokenScript_ReturnsErrors() { var errors = NewValidator().Validate(ConfigWith("this is not C# ~~~")); Assert.NotEmpty(errors); Assert.Contains(errors, e => e.Contains("S1")); } [Fact] public void ValidScript_ReturnsNoErrors() { Assert.Empty(NewValidator().Validate(ConfigWith("return 1 + 1;"))); } [Fact] public void BrokenTriggerExpression_ReturnsErrors() { // One Expression-triggered script whose trigger expression "1 +" does not compile. var cfg = JsonSerializer.Serialize(new FlattenedConfiguration { Scripts = new List { new() { CanonicalName = "S1", Code = "return 1;", TriggerType = "Expression", TriggerConfiguration = JsonSerializer.Serialize(new { expression = "1 +" }) } }, Attributes = new List(), Alarms = new List(), NativeAlarmSources = new List() }); var errors = NewValidator().Validate(cfg); Assert.NotEmpty(errors); Assert.Contains(errors, e => e.Contains("script-trigger-S1")); } [Fact] public void MalformedJson_ReturnsSingleDeserializeError() { var errors = NewValidator().Validate("{ this is not json"); Assert.Single(errors); Assert.Contains("deserialize", errors[0], StringComparison.OrdinalIgnoreCase); } [Fact] public void MissingOnTriggerScript_ReturnsError() { var cfg = JsonSerializer.Serialize(new FlattenedConfiguration { Scripts = new List(), Attributes = new List(), Alarms = new List { new() { CanonicalName = "A1", TriggerType = "HiLo", OnTriggerScriptCanonicalName = "DoesNotExist" } }, NativeAlarmSources = new List() }); var errors = NewValidator().Validate(cfg); Assert.Contains(errors, e => e.Contains("A1") && e.Contains("DoesNotExist")); } }