docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
This commit is contained in:
+7
-1
@@ -4,7 +4,7 @@ using ZB.MOM.WW.ScadaBridge.TemplateEngine.Validation;
|
||||
namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// M9-T32b deploy-time validation: a dangling <c>{"$ref":"lib:Name"}</c> in any
|
||||
/// Deploy-time validation: a dangling <c>{"$ref":"lib:Name"}</c> in any
|
||||
/// validated script parameter/return schema must BLOCK deployment (a
|
||||
/// <see cref="ValidationCategory.SchemaReference"/> error naming the missing ref).
|
||||
/// A valid ref resolved through the supplied seam passes. The check is inert for
|
||||
@@ -15,6 +15,7 @@ public class SchemaRefValidationTests
|
||||
{
|
||||
private readonly ValidationService _sut = new();
|
||||
|
||||
/// <summary>Verifies that a dangling reference in the parameter definitions blocks deployment.</summary>
|
||||
[Fact]
|
||||
public void Validate_DanglingRefInParameterDefinitions_BlocksDeploy()
|
||||
{
|
||||
@@ -40,6 +41,7 @@ public class SchemaRefValidationTests
|
||||
Assert.Contains("MissingLib", error.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a dangling reference in the return definition blocks deployment.</summary>
|
||||
[Fact]
|
||||
public void Validate_DanglingRefInReturnDefinition_BlocksDeploy()
|
||||
{
|
||||
@@ -65,6 +67,7 @@ public class SchemaRefValidationTests
|
||||
&& e.Message.Contains("GoneReturn", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a valid schema reference passes validation.</summary>
|
||||
[Fact]
|
||||
public void Validate_ValidRef_Passes()
|
||||
{
|
||||
@@ -91,6 +94,7 @@ public class SchemaRefValidationTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.SchemaReference);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a schema without references produces no schema reference error.</summary>
|
||||
[Fact]
|
||||
public void Validate_NoRefSchema_NoSchemaReferenceError()
|
||||
{
|
||||
@@ -114,6 +118,7 @@ public class SchemaRefValidationTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.SchemaReference);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that validating without a schema resolver causes no regression.</summary>
|
||||
[Fact]
|
||||
public void Validate_NoResolverSupplied_NoRegression()
|
||||
{
|
||||
@@ -140,6 +145,7 @@ public class SchemaRefValidationTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.SchemaReference);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a cyclic reference blocks deployment without overflowing the stack.</summary>
|
||||
[Fact]
|
||||
public void Validate_CyclicRef_BlocksDeployWithoutOverflow()
|
||||
{
|
||||
|
||||
+5
-1
@@ -3,7 +3,7 @@ using ZB.MOM.WW.ScadaBridge.TemplateEngine.Validation;
|
||||
namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// WP2.5: the verdict cache's eviction policy. Overflow used to <c>Clear()</c> the
|
||||
/// The verdict cache's eviction policy. Overflow used to <c>Clear()</c> the
|
||||
/// whole cache, which re-opened the non-collectible <c>InteractiveAssemblyLoader</c>
|
||||
/// leak the cache exists to bound — every hot script had to be recompiled, and every
|
||||
/// recompile loads another assembly that can never be unloaded. These tests pin the
|
||||
@@ -26,6 +26,7 @@ public class ScriptCompileVerdictCacheEvictionTests
|
||||
private static (bool Ok, string? Error) Lookup(string code, Func<(bool, string?)> factory) =>
|
||||
ScriptCompileVerdictCache.GetOrAdd(Surface, code, factory);
|
||||
|
||||
/// <summary>Verifies that an overflow keeps the hot entry instead of clearing the whole cache.</summary>
|
||||
[Fact]
|
||||
public void Overflow_KeepsHotEntry_AndDoesNotClearEverything()
|
||||
{
|
||||
@@ -66,6 +67,7 @@ public class ScriptCompileVerdictCacheEvictionTests
|
||||
Assert.Equal(compilesBefore, hotCompiles);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an overflow retains recent entries rather than dropping all of them.</summary>
|
||||
[Fact]
|
||||
public void Overflow_RetainsRecentEntries_RatherThanDroppingAll()
|
||||
{
|
||||
@@ -91,6 +93,7 @@ public class ScriptCompileVerdictCacheEvictionTests
|
||||
$"cache retained only {ScriptCompileVerdictCache.Count} entries after eviction");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an overflow keeps the cache within its size bound.</summary>
|
||||
[Fact]
|
||||
public void Overflow_KeepsCacheBounded()
|
||||
{
|
||||
@@ -135,6 +138,7 @@ public class ScriptCompileVerdictCacheEvictionTests
|
||||
$"cache grew to {ScriptCompileVerdictCache.Count} entries under promotion, exceeding its two-generation bound");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the compile surface remains part of the cache key across eviction.</summary>
|
||||
[Fact]
|
||||
public void SurfaceIsPartOfTheKey_AcrossEviction()
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ using ZB.MOM.WW.ScadaBridge.TemplateEngine.Validation;
|
||||
namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// M3.2: the design-time deploy gate now delegates to the shared
|
||||
/// The design-time deploy gate now delegates to the shared
|
||||
/// <c>ZB.MOM.WW.ScadaBridge.ScriptAnalysis</c> analyzer — a real Roslyn semantic
|
||||
/// forbidden-API verdict plus a real CSharpScript compile against the
|
||||
/// runtime-mirroring <c>ScriptCompileSurface</c>. These tests assert the
|
||||
@@ -18,6 +18,7 @@ public class ScriptCompilerTests
|
||||
{
|
||||
private readonly ScriptCompiler _sut = new();
|
||||
|
||||
/// <summary>Verifies that compiling empty code returns a failure.</summary>
|
||||
[Fact]
|
||||
public void TryCompile_EmptyCode_ReturnsFailure()
|
||||
{
|
||||
@@ -26,6 +27,7 @@ public class ScriptCompilerTests
|
||||
Assert.Contains("empty", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that compiling whitespace-only code returns a failure.</summary>
|
||||
[Fact]
|
||||
public void TryCompile_WhitespaceOnly_ReturnsFailure()
|
||||
{
|
||||
@@ -36,6 +38,7 @@ public class ScriptCompilerTests
|
||||
|
||||
// --- Forbidden-API gate (authoritative) ---
|
||||
|
||||
/// <summary>Verifies that compiling code that uses a forbidden API returns a failure.</summary>
|
||||
[Fact]
|
||||
public void TryCompile_ForbiddenApi_ReturnsFailure()
|
||||
{
|
||||
@@ -44,10 +47,11 @@ public class ScriptCompilerTests
|
||||
Assert.Contains("forbidden", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that compiling code that uses several forbidden APIs reports all of them.</summary>
|
||||
[Fact]
|
||||
public void TryCompile_MultipleForbiddenApis_ReportsAll()
|
||||
{
|
||||
// #05-T24: the failure message must enumerate EVERY forbidden API, not
|
||||
// The failure message must enumerate EVERY forbidden API, not
|
||||
// just the first, so an operator fixing one isn't surprised by the next.
|
||||
var result = _sut.TryCompile(
|
||||
"System.IO.File.ReadAllText(\"x\"); System.Diagnostics.Process.Start(\"y\");",
|
||||
@@ -57,6 +61,8 @@ public class ScriptCompilerTests
|
||||
Assert.Contains("Process", result.Error, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that adversarial attempts to bypass the forbidden-API check are still rejected.</summary>
|
||||
/// <param name="code">The adversarial script source under test.</param>
|
||||
[Theory]
|
||||
// Namespace alias — the old substring scan never saw "System.IO." spelled out.
|
||||
[InlineData("using IO = System.IO; IO.File.ReadAllText(\"x\");")]
|
||||
@@ -73,6 +79,7 @@ public class ScriptCompilerTests
|
||||
Assert.Contains("forbidden", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a forbidden type in an otherwise allowed namespace is rejected as forbidden.</summary>
|
||||
[Fact]
|
||||
public void TryCompile_ForbiddenTypeInAllowedNamespace_RejectedAsForbidden()
|
||||
{
|
||||
@@ -87,6 +94,7 @@ public class ScriptCompilerTests
|
||||
Assert.Contains("forbidden", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that using the stopwatch from the allowed diagnostics namespace compiles successfully.</summary>
|
||||
[Fact]
|
||||
public void TryCompile_StopwatchInAllowedDiagnostics_ReturnsSuccess()
|
||||
{
|
||||
@@ -100,6 +108,7 @@ public class ScriptCompilerTests
|
||||
|
||||
// --- Real-compile gate (the win over the old structural-only scan) ---
|
||||
|
||||
/// <summary>Verifies that compiling code with an undefined symbol returns a failure.</summary>
|
||||
[Fact]
|
||||
public void TryCompile_UndefinedSymbol_ReturnsFailure()
|
||||
{
|
||||
@@ -111,6 +120,7 @@ public class ScriptCompilerTests
|
||||
Assert.Contains("compile", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that compiling code with a syntax error returns a failure.</summary>
|
||||
[Fact]
|
||||
public void TryCompile_SyntaxError_ReturnsFailure()
|
||||
{
|
||||
@@ -121,6 +131,7 @@ public class ScriptCompilerTests
|
||||
|
||||
// --- Valid real script against the script API surface ---
|
||||
|
||||
/// <summary>Verifies that a valid script using the API surface compiles successfully.</summary>
|
||||
[Fact]
|
||||
public void TryCompile_ValidScriptUsingApiSurface_ReturnsSuccess()
|
||||
{
|
||||
@@ -137,6 +148,7 @@ public class ScriptCompilerTests
|
||||
|
||||
// --- Trigger-expression gate via ValidationService.CheckExpressionSyntax ---
|
||||
|
||||
/// <summary>Verifies that checking a valid expression returns null.</summary>
|
||||
[Fact]
|
||||
public void CheckExpressionSyntax_ValidExpression_ReturnsNull()
|
||||
{
|
||||
@@ -145,6 +157,7 @@ public class ScriptCompilerTests
|
||||
Assert.Null(error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that checking an expression that uses a forbidden API returns an error message.</summary>
|
||||
[Fact]
|
||||
public void CheckExpressionSyntax_ForbiddenApi_ReturnsMessage()
|
||||
{
|
||||
@@ -153,6 +166,7 @@ public class ScriptCompilerTests
|
||||
Assert.Contains("forbidden", error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that checking the same expression twice serves the second call from the cache.</summary>
|
||||
[Fact]
|
||||
public void CheckExpressionSyntax_SameExpressionTwice_SecondCallIsCacheHit()
|
||||
{
|
||||
@@ -164,6 +178,7 @@ public class ScriptCompilerTests
|
||||
Assert.Equal(hitsBefore + 1, ScriptCompileVerdictCache.Hits); // FAILS today: no cache use
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a verdict cached for the script surface is not reused for the trigger surface.</summary>
|
||||
[Fact]
|
||||
public void CheckExpressionSyntax_ScriptSurfaceVerdict_NotReusedForTriggerSurface()
|
||||
{
|
||||
@@ -176,6 +191,7 @@ public class ScriptCompilerTests
|
||||
Assert.NotNull(error); // green today (no cache), and MUST STAY green after T3 — the T2 key makes it structural
|
||||
}
|
||||
|
||||
/// <summary>Verifies that checking an expression with several forbidden APIs reports all of them.</summary>
|
||||
[Fact]
|
||||
public void CheckExpressionSyntax_MultipleForbiddenApis_ReportsAll()
|
||||
{
|
||||
@@ -186,6 +202,7 @@ public class ScriptCompilerTests
|
||||
Assert.Contains("Process", error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that checking an expression with several compile errors reports all of them.</summary>
|
||||
[Fact]
|
||||
public void CheckExpressionSyntax_MultipleCompileErrors_ReportsAll()
|
||||
{
|
||||
@@ -197,6 +214,7 @@ public class ScriptCompilerTests
|
||||
|
||||
// --- Compile-verdict cache: unchanged code compiles once per process ---
|
||||
|
||||
/// <summary>Verifies that compiling the same code twice serves the second call from the cache.</summary>
|
||||
[Fact]
|
||||
public void TryCompile_SameCodeTwice_SecondCallIsCacheHit()
|
||||
{
|
||||
@@ -209,6 +227,7 @@ public class ScriptCompilerTests
|
||||
Assert.Equal(hitsBefore + 1, ScriptCompileVerdictCache.Hits);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the same code on different surfaces computes separate verdicts.</summary>
|
||||
[Fact]
|
||||
public void GetOrAdd_SameCodeDifferentSurface_ComputesSeparateVerdicts()
|
||||
{
|
||||
@@ -223,6 +242,7 @@ public class ScriptCompilerTests
|
||||
Assert.Equal(2, ScriptCompileVerdictCache.Count);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a cached failure formats the script name separately for each caller.</summary>
|
||||
[Fact]
|
||||
public void TryCompile_CachedFailure_FormatsNameForEachCaller()
|
||||
{
|
||||
|
||||
+65
-7
@@ -7,6 +7,7 @@ public class SemanticValidatorTests
|
||||
{
|
||||
private readonly SemanticValidator _sut = new();
|
||||
|
||||
/// <summary>Verifies that a native alarm source referencing an unknown connection returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_NativeAlarmSource_UnknownConnection_ReturnsError()
|
||||
{
|
||||
@@ -19,6 +20,7 @@ public class SemanticValidatorTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.NativeAlarmSourceInvalid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a native alarm source with an empty source reference returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_NativeAlarmSource_EmptySourceRef_ReturnsError()
|
||||
{
|
||||
@@ -31,6 +33,7 @@ public class SemanticValidatorTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.NativeAlarmSourceInvalid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a native alarm source with a valid binding produces no error.</summary>
|
||||
[Fact]
|
||||
public void Validate_NativeAlarmSource_ValidBinding_NoError()
|
||||
{
|
||||
@@ -43,6 +46,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.NativeAlarmSourceInvalid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a CallScript target that cannot be found returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_CallScriptTargetNotFound_ReturnsError()
|
||||
{
|
||||
@@ -65,6 +69,7 @@ public class SemanticValidatorTests
|
||||
e.Message.Contains("NonExistent"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a CallScript target that exists produces no error.</summary>
|
||||
[Fact]
|
||||
public void Validate_CallScriptTargetExists_NoError()
|
||||
{
|
||||
@@ -82,6 +87,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.CallTargetNotFound);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a CallScript to a composition script referenced by its leaf name produces no error.</summary>
|
||||
[Fact]
|
||||
public void Validate_CallScriptCompositionDelegatedByLeafName_NoError()
|
||||
{
|
||||
@@ -105,10 +111,11 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.CallTargetNotFound);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a CallScript to a composition script referenced by its leaf name emits an advisory warning.</summary>
|
||||
[Fact]
|
||||
public void Validate_CallScriptCompositionDelegatedByLeafName_EmitsAdvisoryWarning()
|
||||
{
|
||||
// #05-T24: a call target accepted ONLY by the composed leaf-name fallback
|
||||
// A call target accepted ONLY by the composed leaf-name fallback
|
||||
// (dynamic child path, not statically verifiable) must surface an advisory
|
||||
// warning rather than being silently accepted.
|
||||
var config = new FlattenedConfiguration
|
||||
@@ -128,6 +135,7 @@ public class SemanticValidatorTests
|
||||
&& w.Message.Contains("leaf name only", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a CallScript to a sibling in the same scope emits no leaf-name warning.</summary>
|
||||
[Fact]
|
||||
public void Validate_CallScriptSameScopeSibling_EmitsNoLeafWarning()
|
||||
{
|
||||
@@ -149,6 +157,7 @@ public class SemanticValidatorTests
|
||||
w.Message.Contains("leaf name only", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a CallScript to an unknown leaf name still returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_CallScriptUnknownLeafName_StillReturnsError()
|
||||
{
|
||||
@@ -169,6 +178,7 @@ public class SemanticValidatorTests
|
||||
e.Category == ValidationCategory.CallTargetNotFound && e.Message.Contains("Typo"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a CallShared target that cannot be found returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_CallSharedTargetNotFound_ReturnsError()
|
||||
{
|
||||
@@ -191,6 +201,7 @@ public class SemanticValidatorTests
|
||||
e.Message.Contains("MissingShared"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a CallShared target that exists produces no error.</summary>
|
||||
[Fact]
|
||||
public void Validate_CallSharedTargetExists_NoError()
|
||||
{
|
||||
@@ -212,6 +223,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.CallTargetNotFound);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a parameter count mismatch returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ParameterCountMismatch_ReturnsError()
|
||||
{
|
||||
@@ -238,8 +250,9 @@ public class SemanticValidatorTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.ParameterMismatch);
|
||||
}
|
||||
|
||||
// ── #21 Argument-type validation ────────────────────────────────────────
|
||||
// ── Argument-type validation ────────────────────────────────────────
|
||||
|
||||
/// <summary>Verifies that passing a string for an integer parameter returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgumentTypeMismatch_StringForInteger_ReturnsError()
|
||||
{
|
||||
@@ -269,6 +282,7 @@ public class SemanticValidatorTests
|
||||
e.Message.Contains("type", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that passing a number for a string parameter returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgumentTypeMismatch_NumberForString_ReturnsError()
|
||||
{
|
||||
@@ -298,6 +312,7 @@ public class SemanticValidatorTests
|
||||
e.Message.Contains("type", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that passing a boolean for an integer parameter returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgumentTypeMismatch_BooleanForInteger_ReturnsError()
|
||||
{
|
||||
@@ -326,6 +341,7 @@ public class SemanticValidatorTests
|
||||
e.Message.Contains("type", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that literals of the correct types produce no error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgumentTypeMatch_CorrectLiterals_NoError()
|
||||
{
|
||||
@@ -354,6 +370,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ParameterMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that passing an integer literal for a float parameter produces no error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgumentType_IntegerLiteralForFloat_NoError()
|
||||
{
|
||||
@@ -381,6 +398,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ParameterMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an expression of unknown type does not produce a false positive.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgumentType_UnknownExpression_NoFalsePositive()
|
||||
{
|
||||
@@ -409,6 +427,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ParameterMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an object initializer argument does not produce a false positive.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgumentType_ObjectInitializerArgument_NoFalsePositive()
|
||||
{
|
||||
@@ -437,6 +456,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ParameterMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an untyped parameter does not produce a false positive.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgumentType_UntypedParameter_NoFalsePositive()
|
||||
{
|
||||
@@ -464,6 +484,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ParameterMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a compound expression starting with a literal does not produce a false positive.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgumentType_CompoundExpressionStartingWithLiteral_NoFalsePositive()
|
||||
{
|
||||
@@ -492,6 +513,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ParameterMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a concatenated string expression does not produce a false positive.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgumentType_ConcatenatedStringExpression_NoFalsePositive()
|
||||
{
|
||||
@@ -520,8 +542,9 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ParameterMismatch);
|
||||
}
|
||||
|
||||
// ── #20 Return-type validation ──────────────────────────────────────────
|
||||
// ── Return-type validation ──────────────────────────────────────────
|
||||
|
||||
/// <summary>Verifies that assigning a boolean result to an integer returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ReturnTypeMismatch_BooleanResultIntoInt_ReturnsError()
|
||||
{
|
||||
@@ -549,6 +572,7 @@ public class SemanticValidatorTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.ReturnTypeMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that assigning a string result to a boolean returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ReturnTypeMismatch_StringResultIntoBool_ReturnsError()
|
||||
{
|
||||
@@ -575,6 +599,7 @@ public class SemanticValidatorTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.ReturnTypeMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a compatible assignment of a return value produces no error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ReturnTypeMatch_CompatibleAssignment_NoError()
|
||||
{
|
||||
@@ -602,6 +627,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ReturnTypeMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that assigning a return value to a var local does not produce a false positive.</summary>
|
||||
[Fact]
|
||||
public void Validate_ReturnType_VarAssignment_NoFalsePositive()
|
||||
{
|
||||
@@ -629,6 +655,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ReturnTypeMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an unused return value does not produce a false positive.</summary>
|
||||
[Fact]
|
||||
public void Validate_ReturnType_UnusedResult_NoFalsePositive()
|
||||
{
|
||||
@@ -656,6 +683,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ReturnTypeMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a call to a script with no declared return type does not produce a false positive.</summary>
|
||||
[Fact]
|
||||
public void Validate_ReturnType_UndeclaredReturn_NoFalsePositive()
|
||||
{
|
||||
@@ -678,11 +706,12 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ReturnTypeMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a return type mismatch on a qualified instance call returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ReturnTypeMismatch_QualifiedInstanceCall_ReturnsError()
|
||||
{
|
||||
// Real-code form: `Instance.CallScript(...)`. The receiver prefix must
|
||||
// be skipped so #20 still sees the typed-local assignment.
|
||||
// be skipped so return-type validation still sees the typed-local assignment.
|
||||
var config = new FlattenedConfiguration
|
||||
{
|
||||
InstanceUniqueName = "Instance1",
|
||||
@@ -706,6 +735,7 @@ public class SemanticValidatorTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.ReturnTypeMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a return type mismatch on a qualified shared call returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ReturnTypeMismatch_QualifiedSharedCall_ReturnsError()
|
||||
{
|
||||
@@ -737,6 +767,7 @@ public class SemanticValidatorTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.ReturnTypeMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a cast expression on a return value does not produce a false positive.</summary>
|
||||
[Fact]
|
||||
public void Validate_ReturnType_CastExpression_NoFalsePositive()
|
||||
{
|
||||
@@ -765,6 +796,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ReturnTypeMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a range violation on a non-numeric attribute returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_RangeViolationOnNonNumeric_ReturnsError()
|
||||
{
|
||||
@@ -790,6 +822,7 @@ public class SemanticValidatorTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.TriggerOperandType);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a range violation on a numeric attribute produces no error.</summary>
|
||||
[Fact]
|
||||
public void Validate_RangeViolationOnNumeric_NoError()
|
||||
{
|
||||
@@ -815,6 +848,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.TriggerOperandType);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an on-trigger script that cannot be found returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_OnTriggerScriptNotFound_ReturnsError()
|
||||
{
|
||||
@@ -837,6 +871,7 @@ public class SemanticValidatorTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.OnTriggerScriptNotFound);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an instance script that calls an alarm on-trigger script returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_InstanceScriptCallsAlarmOnTrigger_ReturnsError()
|
||||
{
|
||||
@@ -867,6 +902,7 @@ public class SemanticValidatorTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.CrossCallViolation);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that call target extraction handles multiple call types.</summary>
|
||||
[Fact]
|
||||
public void ExtractCallTargets_MultipleCallTypes()
|
||||
{
|
||||
@@ -902,6 +938,7 @@ public class SemanticValidatorTests
|
||||
]
|
||||
};
|
||||
|
||||
/// <summary>Verifies that a hi/lo alarm on a non-numeric attribute returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_HiLoOnNonNumericAttribute_ReturnsError()
|
||||
{
|
||||
@@ -916,6 +953,7 @@ public class SemanticValidatorTests
|
||||
&& e.Message.Contains("non-numeric"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a hi/lo alarm on a numeric attribute produces no operand type error.</summary>
|
||||
[Fact]
|
||||
public void Validate_HiLoOnNumericAttribute_NoOperandTypeError()
|
||||
{
|
||||
@@ -927,6 +965,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.TriggerOperandType);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a hi/lo alarm with no setpoints returns a warning.</summary>
|
||||
[Fact]
|
||||
public void Validate_HiLoNoSetpoints_ReturnsWarning()
|
||||
{
|
||||
@@ -941,6 +980,7 @@ public class SemanticValidatorTests
|
||||
&& w.Message.Contains("no setpoints"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a hi/lo alarm whose LoLo setpoint exceeds its Lo setpoint returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_HiLoLoLoGreaterThanLo_ReturnsError()
|
||||
{
|
||||
@@ -955,6 +995,7 @@ public class SemanticValidatorTests
|
||||
&& e.Message.Contains("Lo"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a hi/lo alarm whose Hi setpoint exceeds its HiHi setpoint returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_HiLoHiGreaterThanHiHi_ReturnsError()
|
||||
{
|
||||
@@ -969,6 +1010,7 @@ public class SemanticValidatorTests
|
||||
&& e.Message.Contains("HiHi"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a hi/lo alarm whose low side overlaps its high side returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_HiLoLowSideOverlapsHighSide_ReturnsError()
|
||||
{
|
||||
@@ -983,6 +1025,7 @@ public class SemanticValidatorTests
|
||||
&& e.Message.Contains("overlap"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a hi/lo alarm with only the high side configured produces no ordering error.</summary>
|
||||
[Fact]
|
||||
public void Validate_HiLoOnlyHighSideConfigured_NoOrderingError()
|
||||
{
|
||||
@@ -996,6 +1039,7 @@ public class SemanticValidatorTests
|
||||
e => e.Category == ValidationCategory.TriggerOperandType);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a hi/lo alarm with a negative deadband returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_HiLoNegativeDeadband_ReturnsError()
|
||||
{
|
||||
@@ -1010,6 +1054,7 @@ public class SemanticValidatorTests
|
||||
&& e.Message.Contains("non-negative"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a hi/lo alarm with a zero deadband produces no error.</summary>
|
||||
[Fact]
|
||||
public void Validate_HiLoZeroDeadband_NoError()
|
||||
{
|
||||
@@ -1023,6 +1068,7 @@ public class SemanticValidatorTests
|
||||
e => e.Category == ValidationCategory.TriggerOperandType);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a hi/lo alarm with valid setpoint ordering produces no errors.</summary>
|
||||
[Fact]
|
||||
public void Validate_HiLoValidOrdering_NoErrors()
|
||||
{
|
||||
@@ -1038,8 +1084,9 @@ public class SemanticValidatorTests
|
||||
w => w.Category == ValidationCategory.TriggerOperandType);
|
||||
}
|
||||
|
||||
// ── M2.7 review nits — comment-aware arg tokenizer ─────────────────────
|
||||
// ── Review nits — comment-aware arg tokenizer ─────────────────────
|
||||
|
||||
/// <summary>Verifies that a line comment containing a comma inside the arguments does not produce a false positive.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgSplit_LineCommentWithCommaInsideArgs_NoFalsePositive()
|
||||
{
|
||||
@@ -1068,6 +1115,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ParameterMismatch);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a block comment containing a comma inside the arguments does not produce a false positive.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgSplit_BlockCommentWithCommaInsideArgs_NoFalsePositive()
|
||||
{
|
||||
@@ -1096,8 +1144,9 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ParameterMismatch);
|
||||
}
|
||||
|
||||
// ── M2.7 review nits — stricter numeric-literal inference ───────────────
|
||||
// ── Review nits — stricter numeric-literal inference ───────────────
|
||||
|
||||
/// <summary>Verifies that an identifier with a leading underscore does not produce a false positive.</summary>
|
||||
[Fact]
|
||||
public void Validate_ArgumentType_UnderscoreLeadingIdentifier_NoFalsePositive()
|
||||
{
|
||||
@@ -1126,8 +1175,9 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.ParameterMismatch);
|
||||
}
|
||||
|
||||
// ── MV-5 List attribute semantics ────────────────────────────────────────
|
||||
// ── List attribute semantics ────────────────────────────────────────
|
||||
|
||||
/// <summary>Verifies that a list attribute with a valid element type and default produces no error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ListAttribute_ValidElementTypeAndDefault_NoError()
|
||||
{
|
||||
@@ -1151,6 +1201,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.MissingMetadata);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a list attribute with a null element type returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ListAttribute_NullElementType_ReturnsError()
|
||||
{
|
||||
@@ -1171,6 +1222,7 @@ public class SemanticValidatorTests
|
||||
e.Message.Contains("element type", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a scalar attribute that has an element type returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ScalarAttribute_WithElementType_ReturnsError()
|
||||
{
|
||||
@@ -1191,6 +1243,7 @@ public class SemanticValidatorTests
|
||||
e.Message.Contains("element type", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a list attribute with a non-scalar element type returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ListAttribute_NonScalarElementType_ReturnsError()
|
||||
{
|
||||
@@ -1213,6 +1266,7 @@ public class SemanticValidatorTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a list attribute with a malformed JSON default returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ListAttribute_MalformedJsonDefault_ReturnsError()
|
||||
{
|
||||
@@ -1239,6 +1293,7 @@ public class SemanticValidatorTests
|
||||
e.Message.Contains("default", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a list attribute whose default contains an unparseable element returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ListAttribute_DefaultElementNotParseable_ReturnsError()
|
||||
{
|
||||
@@ -1265,6 +1320,7 @@ public class SemanticValidatorTests
|
||||
e.Message.Contains("Int32"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a list attribute with an empty default produces no default error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ListAttribute_EmptyDefault_NoDefaultError()
|
||||
{
|
||||
@@ -1282,6 +1338,7 @@ public class SemanticValidatorTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.MissingMetadata);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a list attribute used as a range violation operand returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ListAttribute_AsRangeViolationOperand_ReturnsError()
|
||||
{
|
||||
@@ -1311,6 +1368,7 @@ public class SemanticValidatorTests
|
||||
e.Message.Contains("non-numeric"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a list attribute used as a hi/lo operand returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ListAttribute_AsHiLoOperand_ReturnsError()
|
||||
{
|
||||
|
||||
+28
-7
@@ -7,6 +7,7 @@ public class ValidationServiceTests
|
||||
{
|
||||
private readonly ValidationService _sut = new();
|
||||
|
||||
/// <summary>Verifies that a valid configuration returns success.</summary>
|
||||
[Fact]
|
||||
public void Validate_ValidConfig_ReturnsSuccess()
|
||||
{
|
||||
@@ -21,6 +22,7 @@ public class ValidationServiceTests
|
||||
Assert.True(result.IsValid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an empty instance name returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_EmptyInstanceName_ReturnsError()
|
||||
{
|
||||
@@ -35,6 +37,7 @@ public class ValidationServiceTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.FlatteningFailure);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a naming collision returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_NamingCollision_ReturnsError()
|
||||
{
|
||||
@@ -53,6 +56,7 @@ public class ValidationServiceTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.NamingCollision);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a forbidden API returns a compilation error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ForbiddenApi_ReturnsCompilationError()
|
||||
{
|
||||
@@ -74,6 +78,7 @@ public class ValidationServiceTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.ScriptCompilation);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that mismatched braces return a compilation error.</summary>
|
||||
[Fact]
|
||||
public void Validate_MismatchedBraces_ReturnsCompilationError()
|
||||
{
|
||||
@@ -91,6 +96,7 @@ public class ValidationServiceTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.ScriptCompilation);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an alarm referencing a missing attribute returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_AlarmReferencesMissingAttribute_ReturnsError()
|
||||
{
|
||||
@@ -114,6 +120,7 @@ public class ValidationServiceTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.AlarmTriggerReference);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an alarm referencing an existing attribute produces no error.</summary>
|
||||
[Fact]
|
||||
public void Validate_AlarmReferencesExistingAttribute_NoError()
|
||||
{
|
||||
@@ -137,6 +144,7 @@ public class ValidationServiceTests
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.AlarmTriggerReference);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a script trigger referencing a missing attribute returns an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ScriptTriggerReferencesMissingAttribute_ReturnsError()
|
||||
{
|
||||
@@ -160,10 +168,11 @@ public class ValidationServiceTests
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.ScriptTriggerReference);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an unbound data-sourced attribute at design time returns a warning rather than an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_UnboundDataSourceAttribute_DesignTime_ReturnsWarningNotError()
|
||||
{
|
||||
// M2.8 (#23): at template design time (the default, enforceConnectionBindings:false)
|
||||
// At template design time (the default, enforceConnectionBindings:false)
|
||||
// a data-sourced attribute is legitimately unbound — bindings are set later at
|
||||
// instance/deploy time. So this must stay a non-blocking WARNING and IsValid true.
|
||||
var config = new FlattenedConfiguration
|
||||
@@ -187,10 +196,11 @@ public class ValidationServiceTests
|
||||
Assert.True(result.IsValid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an unbound data-sourced attribute at deploy time returns an error and blocks.</summary>
|
||||
[Fact]
|
||||
public void Validate_UnboundDataSourceAttribute_DeployTime_ReturnsErrorAndBlocks()
|
||||
{
|
||||
// M2.8 (#23): the deploy path opts in (enforceConnectionBindings:true). A data-sourced
|
||||
// The deploy path opts in (enforceConnectionBindings:true). A data-sourced
|
||||
// attribute with no binding now gates the deployment as an ERROR (IsValid false).
|
||||
var config = new FlattenedConfiguration
|
||||
{
|
||||
@@ -212,10 +222,11 @@ public class ValidationServiceTests
|
||||
Assert.False(result.IsValid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a static attribute without a binding produces no binding error at deploy time.</summary>
|
||||
[Fact]
|
||||
public void Validate_StaticAttributeWithoutBinding_DeployTime_NoBindingError()
|
||||
{
|
||||
// M2.8 (#23): only DATA-SOURCED attributes require a binding. A static attribute
|
||||
// Only DATA-SOURCED attributes require a binding. A static attribute
|
||||
// (DataSourceReference == null) must remain OK even under deploy-time enforcement.
|
||||
var config = new FlattenedConfiguration
|
||||
{
|
||||
@@ -238,10 +249,11 @@ public class ValidationServiceTests
|
||||
Assert.True(result.IsValid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an attribute bound to an existing site connection produces no binding error at deploy time.</summary>
|
||||
[Fact]
|
||||
public void Validate_BoundToExistingSiteConnection_DeployTime_NoBindingError()
|
||||
{
|
||||
// M2.8 (#23): a data-sourced attribute bound to a connection that exists at the
|
||||
// A data-sourced attribute bound to a connection that exists at the
|
||||
// target site passes the binding gate.
|
||||
var config = new FlattenedConfiguration
|
||||
{
|
||||
@@ -268,10 +280,11 @@ public class ValidationServiceTests
|
||||
Assert.True(result.IsValid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an attribute bound to a non-existent site connection returns an error at deploy time.</summary>
|
||||
[Fact]
|
||||
public void Validate_BoundToNonExistentSiteConnection_DeployTime_ReturnsError()
|
||||
{
|
||||
// M2.8 (#23): a binding pointing at a connection that does NOT exist on the
|
||||
// A binding pointing at a connection that does NOT exist on the
|
||||
// target site is an ERROR that blocks deployment.
|
||||
var config = new FlattenedConfiguration
|
||||
{
|
||||
@@ -298,10 +311,11 @@ public class ValidationServiceTests
|
||||
Assert.False(result.IsValid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the exists-at-site check is inert for a bound attribute when no site is set at deploy time.</summary>
|
||||
[Fact]
|
||||
public void Validate_BoundAttributeWithNoSiteSet_DeployTime_ExistsAtSiteCheckIsInert()
|
||||
{
|
||||
// M2.8 (#23): when siteConnectionNames is null the "exists at site" half of the
|
||||
// When siteConnectionNames is null the "exists at site" half of the
|
||||
// binding check stays inert — a properly-bound data-sourced attribute must NOT
|
||||
// produce a ConnectionBinding error, even under deploy-time enforcement.
|
||||
// This pins the contract: passing enforce:true + siteConnectionNames:null is safe
|
||||
@@ -328,6 +342,7 @@ public class ValidationServiceTests
|
||||
Assert.True(result.IsValid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an empty configuration returns a warning.</summary>
|
||||
[Fact]
|
||||
public void Validate_EmptyConfig_ReturnsWarning()
|
||||
{
|
||||
@@ -337,12 +352,13 @@ public class ValidationServiceTests
|
||||
Assert.Contains(result.Warnings, w => w.Category == ValidationCategory.FlatteningFailure);
|
||||
}
|
||||
|
||||
// --- M9-T28a: per-trigger AnalysisKind (Advisory default | Strict escalates) ---
|
||||
// --- Per-trigger AnalysisKind (Advisory default | Strict escalates) ---
|
||||
// The only currently-advisory finding in CheckExpressionTrigger is the blank/empty
|
||||
// expression (which "will never fire"). Advisory keeps it a non-blocking warning;
|
||||
// Strict promotes it to a deploy-blocking error. The kind rides the existing
|
||||
// trigger-config JSON ({"expression":"...","analysisKind":"Strict"}) — no migration.
|
||||
|
||||
/// <summary>Verifies that a blank expression trigger under the default advisory kind warns but passes.</summary>
|
||||
[Fact]
|
||||
public void Validate_ExpressionTrigger_BlankExpression_AdvisoryDefault_WarnsButPasses()
|
||||
{
|
||||
@@ -368,6 +384,7 @@ public class ValidationServiceTests
|
||||
Assert.True(result.IsValid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a blank expression trigger under the strict kind fails with an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ExpressionTrigger_BlankExpression_StrictKind_FailsWithError()
|
||||
{
|
||||
@@ -392,6 +409,7 @@ public class ValidationServiceTests
|
||||
Assert.False(result.IsValid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a blank script expression trigger under the strict kind fails with an error.</summary>
|
||||
[Fact]
|
||||
public void Validate_ScriptExpressionTrigger_BlankExpression_StrictKind_FailsWithError()
|
||||
{
|
||||
@@ -416,6 +434,7 @@ public class ValidationServiceTests
|
||||
Assert.False(result.IsValid);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a valid expression trigger passes under both kinds.</summary>
|
||||
[Fact]
|
||||
public void Validate_ExpressionTrigger_ValidExpression_PassesUnderBothKinds()
|
||||
{
|
||||
@@ -463,6 +482,7 @@ public class ValidationServiceTests
|
||||
Assert.DoesNotContain(strictResult.Warnings, w => w.Category == ValidationCategory.AlarmTriggerReference);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that skipping compilation skips the expression trigger syntax check.</summary>
|
||||
[Fact]
|
||||
public void Validate_SkipCompilation_SkipsExpressionTriggerSyntaxCheck()
|
||||
{
|
||||
@@ -473,6 +493,7 @@ public class ValidationServiceTests
|
||||
Assert.Contains(full.Errors, e => e.Message.Contains("failed validation")); // deploy gate unchanged
|
||||
}
|
||||
|
||||
/// <summary>Verifies that skipping compilation still checks the expression attribute references.</summary>
|
||||
[Fact]
|
||||
public void Validate_SkipCompilation_StillChecksExpressionAttributeReferences()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user