Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Calculation.Tests/CalculationScriptingOptionsResolutionTests.cs
T
Joseph Doherty 4615495f01 feat(scripting): bind the Scripting section node-wide and clamp its knobs
Program.cs binds ScriptingOptions from Scripting:* once, clamps every
numeric knob through ScriptingOptions.Sanitized (logging a Warning per
adjustment) and registers the one instance. It now reaches all three
script seams: RoslynVirtualTagEvaluator, the scripted-alarm engine
built in DriverHostActor (new trailing Props parameter, wired in
WithOtOpcUaRuntimeActors) and every Calculation driver (node options
as the base, per-driver config keys override one knob on a copy).

ScriptRunGuard sanitizes again in its ctor and computes the stuck
threshold with a saturating multiply, so an absurd
StuckOrphanQuarantineFactor can no longer throw OverflowException on
a refused run.

Refs #731 #734
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
2026-10-22 14:24:53 -04:00

57 lines
2.3 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Scripting;
namespace ZB.MOM.WW.OtOpcUa.Driver.Calculation.Tests;
/// <summary>
/// The Calculation driver's run-guard options: the node-wide <c>Scripting</c> section is the base, and a key
/// authored in the driver's own config overrides only that knob. Before the section was bound, a Calculation
/// driver always ran on the compiled defaults whatever the node configured.
/// </summary>
[Trait("Category", "Unit")]
public sealed class CalculationScriptingOptionsResolutionTests
{
private static readonly ScriptingOptions Node = new()
{
MaxAllocBytesPerRun = 8L * 1024 * 1024,
QuarantineAfterConsecutiveTimeouts = 7,
StuckOrphanQuarantineFactor = 4,
};
[Fact]
public void No_driver_keys_and_no_node_options_resolves_null_so_the_guard_uses_its_defaults() =>
CalculationDriverFactoryExtensions.ResolveScripting(null, null, null).ShouldBeNull();
[Fact]
public void No_driver_keys_inherits_every_node_knob()
{
var resolved = CalculationDriverFactoryExtensions.ResolveScripting(null, null, Node).ShouldNotBeNull();
resolved.MaxAllocBytesPerRun.ShouldBe(Node.MaxAllocBytesPerRun);
resolved.QuarantineAfterConsecutiveTimeouts.ShouldBe(7);
resolved.StuckOrphanQuarantineFactor.ShouldBe(4);
}
[Fact]
public void A_driver_key_overrides_only_its_own_knob_and_the_node_object_is_not_mutated()
{
var resolved = CalculationDriverFactoryExtensions.ResolveScripting(null, 2, Node).ShouldNotBeNull();
resolved.QuarantineAfterConsecutiveTimeouts.ShouldBe(2);
resolved.MaxAllocBytesPerRun.ShouldBe(Node.MaxAllocBytesPerRun, "the unauthored key keeps the node value");
resolved.StuckOrphanQuarantineFactor.ShouldBe(4, "the stuck factor has no driver key; it is node-wide");
resolved.ShouldNotBeSameAs(Node);
Node.QuarantineAfterConsecutiveTimeouts.ShouldBe(7);
}
[Fact]
public void A_driver_key_with_no_node_options_layers_on_the_compiled_defaults()
{
var resolved = CalculationDriverFactoryExtensions.ResolveScripting(0L, null, null).ShouldNotBeNull();
resolved.MaxAllocBytesPerRun.ShouldBe(0);
resolved.QuarantineAfterConsecutiveTimeouts.ShouldBe(ScriptingOptions.DefaultQuarantineAfterConsecutiveTimeouts);
}
}