feat(scriptanalysis): M3.1 shared trust validator + compiler + compile surfaces + tests

This commit is contained in:
Joseph Doherty
2026-06-16 19:18:39 -04:00
parent 0cc8642cfa
commit 4f2b17ce6d
10 changed files with 1100 additions and 0 deletions
@@ -0,0 +1,72 @@
using ZB.MOM.WW.ScadaBridge.ScriptAnalysis;
namespace ZB.MOM.WW.ScadaBridge.ScriptAnalysis.Tests;
/// <summary>
/// M3.1: parse + compile gate tests. The "representative real script" corpus is
/// the PRIMARY guard that <see cref="ScriptCompileSurface"/> faithfully mirrors
/// the runtime <c>ScriptGlobals</c> surface — if a member or signature drifts,
/// the corpus stops binding and this test fails.
/// </summary>
public class RoslynScriptCompilerTests
{
[Fact]
public void ParseDiagnostics_NonEmpty_ForSyntaxError()
{
Assert.NotEmpty(RoslynScriptCompiler.ParseDiagnostics("var x = ;"));
}
[Fact]
public void ParseDiagnostics_Empty_ForValidSyntax()
{
Assert.Empty(RoslynScriptCompiler.ParseDiagnostics("var x = 1;"));
}
[Fact]
public void Compile_NonEmpty_ForUndefinedSymbol()
{
var code = "var x = NoSuchThing.Foo();";
Assert.NotEmpty(RoslynScriptCompiler.Compile(code, typeof(ScriptCompileSurface)));
}
[Fact]
public void Compile_Empty_ForRepresentativeRealScript()
{
const string code = """
var temp = Attributes["Temperature"];
Attributes["Setpoint"] = 42;
var r = await ExternalSystem.Call("erp", "sync");
var op = await Database.CachedWrite("hist", "INSERT ...");
await Notify.To("ops").Send("subj", "msg");
var shared = await Scripts.CallShared("Helper");
var child = Children["Pump"].Attributes["Speed"];
// Widen coverage across the rest of the surface.
var attr = await Instance.GetAttribute("Temperature");
await Instance.SetAttribute("Setpoint", "43");
var track = await Instance.Tracking.Status(op);
var parentSpeed = Parent?.Attributes["Speed"];
var alarmName = Alarm?.Name;
var p = Parameters;
var ct = CancellationToken;
var status = await Notify.Status("notif-id");
var cachedCall = await ExternalSystem.CachedCall("erp", "ping");
var resolved = Attributes.Resolve("Temperature");
var conn = await Database.Connection("hist");
var scope = Scope;
""";
var diagnostics = RoslynScriptCompiler.Compile(code, typeof(ScriptCompileSurface));
Assert.Empty(diagnostics);
}
[Fact]
public void Compile_Empty_ForTriggerExpression()
{
const string expr =
"Attributes[\"Temp\"] != null && (int)(Children[\"P\"].Attributes[\"S\"] ?? 0) > 5";
var diagnostics = RoslynScriptCompiler.Compile(expr, typeof(TriggerCompileSurface));
Assert.Empty(diagnostics);
}
}