test(scripting): pin the Scripting section bind behind one helper

Review m-1. The bind moves from Program.cs into
Host.Configuration.ScriptingOptionsBinding.Bind, which binds the
section and clamps it. Program.cs calls it, and
ScriptingOptionsBindingTests drives it with in-memory configuration.
Every documented key takes a non-default value, so a misspelled
property or key goes red instead of silently falling back to the
defaults. The tests also cover a clamped bind (1e300 / 1024 / 0, three
adjustments), an absent section, and the sibling LogFilePath /
LogTopicMinLevel keys leaving the bind untouched.

Refs #731 #734
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
Joseph Doherty
2026-10-22 15:14:02 -04:00
parent 89204ac0fa
commit 4d8de20050
3 changed files with 89 additions and 2 deletions
@@ -0,0 +1,23 @@
using Microsoft.Extensions.Configuration;
using ZB.MOM.WW.OtOpcUa.Core.Scripting;
namespace ZB.MOM.WW.OtOpcUa.Host.Configuration;
/// <summary>
/// Binds the node-wide <c>Scripting</c> section into <see cref="ScriptingOptions"/> and clamps it
/// (<see cref="ScriptingOptions.Sanitized"/>). One place, so <c>Program.cs</c> and its unit test bind the same
/// way and a misspelled key cannot silently fall back to the compiled defaults unnoticed.
/// </summary>
public static class ScriptingOptionsBinding
{
/// <summary>Binds and clamps the <c>Scripting</c> section.</summary>
/// <param name="configuration">The host configuration.</param>
/// <param name="adjustments">One line per clamped value, for the caller to log as a Warning.</param>
/// <returns>The clamped options; the compiled defaults when the section is absent.</returns>
public static ScriptingOptions Bind(IConfiguration configuration, out IReadOnlyList<string> adjustments)
{
ArgumentNullException.ThrowIfNull(configuration);
var bound = configuration.GetSection(ScriptingOptions.SectionName).Get<ScriptingOptions>() ?? new ScriptingOptions();
return bound.Sanitized(out adjustments);
}
}
+2 -2
View File
@@ -370,8 +370,8 @@ if (hasDriver)
// Warning rather than an OverflowException inside a script run. The one instance is shared by every script
// seam on the node: the virtual-tag evaluator below, the scripted-alarm engine (DriverHostActor, via
// WithOtOpcUaRuntimeActors) and every Calculation driver (DriverFactoryBootstrap).
var scriptingOptions = (builder.Configuration.GetSection(ScriptingOptions.SectionName).Get<ScriptingOptions>()
?? new ScriptingOptions()).Sanitized(out var scriptingAdjustments);
var scriptingOptions = ZB.MOM.WW.OtOpcUa.Host.Configuration.ScriptingOptionsBinding.Bind(
builder.Configuration, out var scriptingAdjustments);
foreach (var adjustment in scriptingAdjustments)
Log.Warning("Scripting misconfiguration detected at startup: {Adjustment}", adjustment);
builder.Services.AddSingleton(scriptingOptions);