using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Scripting; namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests; /// /// Covers the argument-validation guards. The /// guards are the only call-site protection against a typo / mis-wired context /// type silently producing a sandbox missing the concrete context's assembly /// reference; without coverage, the guards could be deleted by a refactor without /// any test failing. (Core.Scripting-011.) /// [Trait("Category", "Unit")] public sealed class ScriptSandboxBuildTests { [Fact] public void Null_context_type_throws_ArgumentNullException() { Should.Throw(() => ScriptSandbox.Build(null!)); } [Fact] public void Non_ScriptContext_type_throws_ArgumentException() { // ScriptSandbox must reject types that don't derive from ScriptContext — // ScriptGlobals is constrained where TContext : ScriptContext, so // sneaking a non-derived type past Build would later blow up inside Roslyn. Should.Throw(() => ScriptSandbox.Build(typeof(string))) .ParamName.ShouldBe("contextType"); } [Fact] public void Abstract_ScriptContext_base_type_is_accepted() { // The base ScriptContext type satisfies the IsAssignableFrom check, so the // factory must not reject it even though it cannot be instantiated directly. // Callers wiring a base-typed sandbox up for diagnostics rely on this. var options = ScriptSandbox.Build(typeof(ScriptContext)); options.ShouldNotBeNull(); } [Fact] public void Concrete_subclass_is_accepted_and_its_assembly_referenced() { // The concrete context type's assembly must end up in the allow-listed // references — otherwise Roslyn cannot resolve ScriptGlobals at // compile. We can't easily inspect the ScriptOptions metadata references // by-assembly cross-version, so we exercise the end-to-end path instead: a // script compiled against FakeScriptContext must successfully reach a // FakeScriptContext member. var evaluator = ScriptEvaluator.Compile( """return (double)ctx.GetTag("X").Value;"""); evaluator.ShouldNotBeNull(); } }