fix(core-scripting): resolve Low code-review findings (Core.Scripting-005,006,008,009,011)
- Core.Scripting-005: DependencyExtractor.HandleTagCall now recognises raw-string literal paths by checking the StringLiteralExpression node kind instead of the legacy StringLiteralToken kind. - Core.Scripting-006: scope CompiledScriptCache failed-compile eviction with TryRemove(KeyValuePair) so a racing retry entry is not evicted. - Core.Scripting-008: document the per-publish assembly accretion as an accepted limitation in docs/VirtualTags.md. - Core.Scripting-009: enumerate the authoritative deny-list (namespace prefixes + type-granular denies) in the Phase 7 decision-#6 entry to match ForbiddenTypeAnalyzer. - Core.Scripting-011: pin ScriptSandbox.Build, ScriptContext.Deadband boundary semantics, and end-to-end factory + companion-sink integration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Scripting;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Covers the <see cref="ScriptSandbox.Build"/> 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.)
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class ScriptSandboxBuildTests
|
||||
{
|
||||
[Fact]
|
||||
public void Null_context_type_throws_ArgumentNullException()
|
||||
{
|
||||
Should.Throw<ArgumentNullException>(() => ScriptSandbox.Build(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Non_ScriptContext_type_throws_ArgumentException()
|
||||
{
|
||||
// ScriptSandbox must reject types that don't derive from ScriptContext —
|
||||
// ScriptGlobals<TContext> is constrained where TContext : ScriptContext, so
|
||||
// sneaking a non-derived type past Build would later blow up inside Roslyn.
|
||||
Should.Throw<ArgumentException>(() => 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<TContext> 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<FakeScriptContext, double>.Compile(
|
||||
"""return (double)ctx.GetTag("X").Value;""");
|
||||
evaluator.ShouldNotBeNull();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user