using System.Reflection;
using ZB.MOM.WW.ScadaBridge.ScriptAnalysis;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Scripts;
///
/// M3.3: compile-surface parity guard. The shared
/// / are
/// compile-only stubs the design-time deploy gate binds candidate scripts
/// against; they must mirror the real SiteRuntime /
/// bind surfaces. If a member a script can
/// reference on the real globals is missing from the compile surface, a script
/// that uses it would pass the design-time gate but fail at the site — so the
/// compile surface's public top-level member NAMES must be a SUPERSET of the
/// real globals' names. This test fails loudly (listing the missing names) when
/// the surface drifts behind the runtime globals.
///
///
/// Top-level member-name parity is sufficient: the design-time compile against
/// the surface catches deeper signature mismatches itself; this guard only
/// ensures every entry point a script can name on the real globals exists on the
/// stub.
///
///
public class CompileSurfaceParityTests
{
[Fact]
public void ScriptCompileSurface_MemberNames_AreSupersetOf_ScriptGlobals()
{
AssertSurfaceCoversGlobals(
surface: typeof(ScriptCompileSurface),
globals: typeof(ScriptGlobals));
}
[Fact]
public void TriggerCompileSurface_MemberNames_AreSupersetOf_TriggerExpressionGlobals()
{
AssertSurfaceCoversGlobals(
surface: typeof(TriggerCompileSurface),
globals: typeof(TriggerExpressionGlobals));
}
///
/// Deeper guard than the top-level name-superset check above: for each
/// runtime script-accessor type (,
/// , ) every
/// public instance METHOD must have a same-name / same-arity counterpart on
/// its compile-surface mirror. This catches the regression that motivated this
/// test (the WaitAsync overloads were present on the runtime
/// but missing from
/// , so scripts that
/// awaited them passed the design-time gate yet would have failed at the site).
///
///
/// Matching is by NAME + PARAMETER COUNT only — the mirror uses
/// object?-vs-mirror-type substitutions, so exact parameter-type identity
/// is intentionally NOT required; the guard is about API presence, not type
/// equality (the design-time compile catches deeper signature drift itself).
/// To still catch a single dropped overload within an overload set that shares
/// an arity (e.g. the value-form vs predicate-form WaitAsync, both
/// 4-parameter), the assertion is by COUNT: the mirror must expose at least as
/// many overloads for each (name, arity) as the runtime accessor declares.
///
///
[Theory]
[MemberData(nameof(AccessorMirrorPairs))]
public void CompileAccessorMirror_Covers_RuntimeAccessor_MethodArities(
Type runtimeAccessor, Type compileMirror)
{
var mirrorCounts = PublicInstanceMethodArityCounts(compileMirror);
var runtimeCounts = PublicInstanceMethodArityCounts(runtimeAccessor);
var shortfalls = runtimeCounts
.Where(kvp => mirrorCounts.GetValueOrDefault(kvp.Key) < kvp.Value)
.OrderBy(kvp => kvp.Key.Name, StringComparer.Ordinal)
.ThenBy(kvp => kvp.Key.Arity)
.Select(kvp =>
$"{kvp.Key.Name}({kvp.Key.Arity} param(s)): runtime has {kvp.Value} "
+ $"overload(s), mirror has {mirrorCounts.GetValueOrDefault(kvp.Key)}")
.ToList();
Assert.True(
shortfalls.Count == 0,
$"Compile surface mirror '{compileMirror.Name}' under-covers {shortfalls.Count} "
+ $"method group(s) on runtime accessor '{runtimeAccessor.Name}': "
+ $"{string.Join("; ", shortfalls)}. "
+ "The compile-only mirror must expose a same-name / same-arity method for "
+ "every public instance method a script can call on the runtime accessor — "
+ "add the missing method(s) to the ScriptAnalysis compile surface so a "
+ "script using them cannot pass the design-time gate then fail at the site.");
}
///
/// Runtime script-accessor type ↔ compile-surface mirror pairs guarded by
/// .
///
public static IEnumerable