using Microsoft.Extensions.Logging.Abstractions; using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts; namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Scripts; /// /// WP-19: Script Trust Model tests — validates forbidden API detection and compilation. /// /// As of the M3.3 consolidation, ScriptCompilationService.ValidateTrustModel /// delegates its forbidden-API verdict to the shared authoritative /// ScriptAnalysis.ScriptTrustValidator, which is stricter than SiteRuntime's /// original deny-list: ALL of System.Net is forbidden (not just Sockets/Http), /// plus reflection gateways, dynamic, Activator, /// System.Runtime.InteropServices and Microsoft.Win32. Only /// System.Diagnostics.Process is blocked under System.Diagnostics — /// Stopwatch stays allowed. The real execution-path compile against /// ScriptGlobals / TriggerExpressionGlobals is unchanged. /// public class ScriptCompilationServiceTests { private readonly ScriptCompilationService _service; public ScriptCompilationServiceTests() { _service = new ScriptCompilationService(NullLogger.Instance); } [Fact] public void Compile_ValidScript_Succeeds() { var result = _service.Compile("test", "1 + 1"); Assert.True(result.IsSuccess); Assert.NotNull(result.CompiledScript); Assert.Empty(result.Errors); } [Fact] public void Compile_InvalidSyntax_ReturnsErrors() { var result = _service.Compile("bad", "this is not valid C# {{{"); Assert.False(result.IsSuccess); Assert.NotEmpty(result.Errors); } [Fact] public void ValidateTrustModel_SystemIO_Forbidden() { var violations = _service.ValidateTrustModel("System.IO.File.ReadAllText(\"test\")"); Assert.NotEmpty(violations); Assert.Contains(violations, v => v.Contains("System.IO")); } [Fact] public void ValidateTrustModel_Process_Forbidden() { var violations = _service.ValidateTrustModel( "System.Diagnostics.Process.Start(\"cmd\")"); Assert.NotEmpty(violations); } [Fact] public void ValidateTrustModel_Reflection_Forbidden() { var violations = _service.ValidateTrustModel( "typeof(string).GetType().GetMethods(System.Reflection.BindingFlags.Public)"); Assert.NotEmpty(violations); } [Fact] public void ValidateTrustModel_Sockets_Forbidden() { var violations = _service.ValidateTrustModel( "new System.Net.Sockets.TcpClient()"); Assert.NotEmpty(violations); } [Fact] public void ValidateTrustModel_HttpClient_Forbidden() { var violations = _service.ValidateTrustModel( "new System.Net.Http.HttpClient()"); Assert.NotEmpty(violations); } [Fact] public void ValidateTrustModel_AsyncAwait_Allowed() { // System.Threading.Tasks should be allowed (async/await support) var violations = _service.ValidateTrustModel( "await System.Threading.Tasks.Task.Delay(100)"); Assert.Empty(violations); } [Fact] public void ValidateTrustModel_CancellationToken_Allowed() { var violations = _service.ValidateTrustModel( "System.Threading.CancellationToken.None"); Assert.Empty(violations); } [Fact] public void ValidateTrustModel_CleanCode_NoViolations() { var code = @" var x = 1 + 2; var list = new List { 1, 2, 3 }; var sum = list.Sum(); sum"; var violations = _service.ValidateTrustModel(code); Assert.Empty(violations); } [Fact] public void Compile_ForbiddenApi_FailsValidation() { var result = _service.Compile("evil", "System.IO.File.Delete(\"/tmp/test\")"); Assert.False(result.IsSuccess); Assert.NotEmpty(result.Errors); } // ── M3.3: stricter shared-validator behavior ── [Fact] public void ValidateTrustModel_SystemNetDns_Forbidden() { // The shared validator forbids ALL of System.Net — not just Sockets/Http. // System.Net.Dns was allowed under the old SiteRuntime list; now blocked. var violations = _service.ValidateTrustModel( "System.Net.Dns.GetHostName()"); Assert.NotEmpty(violations); Assert.Contains(violations, v => v.Contains("System.Net")); } [Fact] public void ValidateTrustModel_ReflectionGatewayViaPermittedType_Forbidden() { // typeof(x).Assembly.GetType(...) never spells a forbidden namespace, but // the shared validator rejects the reflection-gateway members regardless of // receiver — this was NOT caught by the old SiteRuntime list. var violations = _service.ValidateTrustModel( "typeof(string).Assembly.GetType(\"System.IO.File\")"); Assert.NotEmpty(violations); } [Fact] public void ValidateTrustModel_Dynamic_Forbidden() { var violations = _service.ValidateTrustModel("dynamic d = 1; return d;"); Assert.NotEmpty(violations); } [Fact] public void ValidateTrustModel_Activator_Forbidden() { var violations = _service.ValidateTrustModel( "Activator.CreateInstance(typeof(string))"); Assert.NotEmpty(violations); } [Fact] public void ValidateTrustModel_InteropServices_Forbidden() { var violations = _service.ValidateTrustModel( "System.Runtime.InteropServices.Marshal.SizeOf()"); Assert.NotEmpty(violations); } [Fact] public void ValidateTrustModel_Stopwatch_Allowed() { // Only System.Diagnostics.Process is blocked under System.Diagnostics — // Stopwatch stays allowed. var violations = _service.ValidateTrustModel( "var sw = System.Diagnostics.Stopwatch.StartNew(); return sw.ElapsedMilliseconds;"); Assert.Empty(violations); } }