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:
Joseph Doherty
2026-10-07 15:05:49 -04:00
parent 0ffd4bdbea
commit 513fa1aa16
998 changed files with 17896 additions and 2395 deletions
@@ -4,6 +4,9 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.ScriptAnalysis;
[Collection("Sandbox worker processes")]
public class BoundedSandboxOutputTests
{
/// <summary>Verifies that oversized console output and return values are explicitly truncated.</summary>
/// <param name="returnExpression">The script expression whose value is returned.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
[Theory]
[InlineData("block")]
[InlineData("new { Data = block }")]
@@ -21,6 +24,7 @@ public class BoundedSandboxOutputTests
Assert.True(System.Text.Encoding.UTF8.GetByteCount(result.ReturnValueJson!) < 32000);
}
/// <summary>Verifies that concurrent large writes retain only the configured character budget.</summary>
[Fact]
public void ConcurrentRepeatedLargeWrites_RetainOnlyBudget()
{
@@ -6,6 +6,7 @@ public class JsonSchemaShapeParserTests
{
// ── JSON Schema (post-migration) ─────────────────────────────────────────
/// <summary>Verifies that a JSON schema parses into scalar parameters with their required flags.</summary>
[Fact]
public void Parameters_JsonSchema_ScalarsAndRequired()
{
@@ -24,6 +25,7 @@ public class JsonSchemaShapeParserTests
p => { Assert.Equal("active", p.Name); Assert.Equal("Boolean", p.Type); Assert.True(p.Required); });
}
/// <summary>Verifies that a JSON schema array of strings becomes a list of strings.</summary>
[Fact]
public void Parameters_JsonSchema_ArrayOfStringsBecomesListString()
{
@@ -40,12 +42,14 @@ public class JsonSchemaShapeParserTests
Assert.False(tags.Required);
}
/// <summary>Verifies that a JSON schema number return type parses as a number.</summary>
[Fact]
public void Return_JsonSchema_Number()
{
Assert.Equal("Float", JsonSchemaShapeParser.ParseReturnType(@"{""type"":""number""}"));
}
/// <summary>Verifies that a JSON schema array of integers return type parses as a list of integers.</summary>
[Fact]
public void Return_JsonSchema_ArrayOfIntegers()
{
@@ -55,6 +59,7 @@ public class JsonSchemaShapeParserTests
// ── Legacy flat shape (pre-migration safety net) ─────────────────────────
/// <summary>Verifies that the legacy flat array parameter format still parses.</summary>
[Fact]
public void Parameters_Legacy_FlatArrayStillParses()
{
@@ -66,6 +71,7 @@ public class JsonSchemaShapeParserTests
p => { Assert.Equal("y", p.Name); Assert.Equal("String", p.Type); Assert.False(p.Required); });
}
/// <summary>Verifies that the legacy list sentinel return format still parses.</summary>
[Fact]
public void Return_Legacy_ListSentinelStillParses()
{
@@ -75,6 +81,7 @@ public class JsonSchemaShapeParserTests
// ── Edge cases ────────────────────────────────────────────────────────────
/// <summary>Verifies that null parameters yield an empty list.</summary>
[Fact]
public void Parameters_Null_ReturnsEmpty()
{
@@ -83,6 +90,7 @@ public class JsonSchemaShapeParserTests
Assert.Empty(JsonSchemaShapeParser.ParseParameters(" "));
}
/// <summary>Verifies that malformed parameters yield an empty list.</summary>
[Fact]
public void Parameters_Malformed_ReturnsEmpty()
{
@@ -90,6 +98,7 @@ public class JsonSchemaShapeParserTests
Assert.Empty(JsonSchemaShapeParser.ParseParameters("42"));
}
/// <summary>Verifies that a null return definition yields null.</summary>
[Fact]
public void Return_Null_ReturnsNull()
{
@@ -97,6 +106,7 @@ public class JsonSchemaShapeParserTests
Assert.Null(JsonSchemaShapeParser.ParseReturnType(""));
}
/// <summary>Verifies that a schema with no properties yields an empty parameter list.</summary>
[Fact]
public void Parameters_SchemaWithNoProperties_ReturnsEmpty()
{
@@ -4,7 +4,7 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.ScriptAnalysis;
/// <summary>
/// Regression tests for the <c>SandboxConsoleCapture</c> writer that the Test Run
/// sandbox installs on <c>Console.Out</c>/<c>Console.Error</c>. CentralUI-030
/// sandbox installs on <c>Console.Out</c>/<c>Console.Error</c>. A past defect
/// surfaced an intra-script concurrency hazard: a sandboxed script can fan out
/// work with <c>Task.WhenAll</c> / <c>Task.Run</c> and every child task inherits
/// the capture <c>StringWriter</c> via <c>AsyncLocal</c>; <c>StringWriter</c> is
@@ -14,10 +14,11 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.ScriptAnalysis;
public class SandboxConsoleCaptureTests
{
/// <summary>
/// CentralUI-030: a capture scope shared across <c>Task.WhenAll</c> child
/// A capture scope shared across <c>Task.WhenAll</c> child
/// tasks must serialise writes so the resulting transcript contains exactly
/// the expected number of lines without character-level interleaving.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task BeginCapture_ConcurrentWritesFromTasks_DoNotCorruptBuffer()
{
@@ -66,7 +67,7 @@ public class SandboxConsoleCaptureTests
/// <summary>
/// Sanity check: the most basic capture happy-path still works after the
/// CentralUI-030 lock was introduced.
/// concurrency lock was introduced.
/// </summary>
[Fact]
public void BeginCapture_SingleThreadedWrites_AreCaptured()
@@ -5,7 +5,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Types;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.ScriptAnalysis;
/// <summary>
/// Deferred-work #14 (full fidelity): the Test-Run sandbox routes value-equality
/// Full-fidelity wait support: the Test-Run sandbox routes value-equality
/// <c>WaitAsync</c>/<c>WaitForAsync</c> to the bound instance via
/// <see cref="ISandboxInstanceGateway"/>; predicate overloads and unbound runs still
/// throw a clearly-labelled <see cref="ScriptSandboxException"/>.
@@ -15,6 +15,8 @@ public class SandboxWaitForAttributeTests
private static SandboxAttributeAccessor BoundAccessor(ISandboxInstanceGateway gateway) =>
new(new SandboxInstanceContext(gateway), prefix: string.Empty);
/// <summary>Verifies that wait async, value form, routes through gateway, and returns matched.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task WaitAsync_ValueForm_RoutesThroughGateway_AndReturnsMatched()
{
@@ -31,6 +33,8 @@ public class SandboxWaitForAttributeTests
"Flag", AttributeValueCodec.Encode(true), TimeSpan.FromSeconds(5), false, Arg.Any<CancellationToken>());
}
/// <summary>Verifies that wait for async, value form, passes require good quality, and returns full result.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task WaitForAsync_ValueForm_PassesRequireGoodQuality_AndReturnsFullResult()
{
@@ -48,6 +52,8 @@ public class SandboxWaitForAttributeTests
"Done", AttributeValueCodec.Encode(7), TimeSpan.FromSeconds(3), true, Arg.Any<CancellationToken>());
}
/// <summary>Verifies that wait async, predicate form, throws.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task WaitAsync_PredicateForm_Throws()
{
@@ -58,6 +64,8 @@ public class SandboxWaitForAttributeTests
Assert.Contains("predicate", ex.Message, StringComparison.OrdinalIgnoreCase);
}
/// <summary>Verifies that wait for async, predicate form, throws.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task WaitForAsync_PredicateForm_Throws()
{
@@ -67,6 +75,8 @@ public class SandboxWaitForAttributeTests
() => accessor.WaitForAsync("Done", v => v != null, TimeSpan.FromSeconds(1)));
}
/// <summary>Verifies that wait async, value form, unbound instance, throws.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task WaitAsync_ValueForm_UnboundInstance_Throws()
{
@@ -10,6 +10,8 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.ScriptAnalysis;
[Collection("Sandbox worker processes")]
public class SandboxWorkerIsolationTests
{
/// <summary>Verifies that four busy workers reject a queued fifth request without starting another process.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task FourBusyWorkers_RejectQueuedFifthWithoutStartingAnotherProcess()
{
@@ -36,6 +38,8 @@ public class SandboxWorkerIsolationTests
foreach (var result in await Task.WhenAll(workers)) Assert.True(result.Success, result.Error);
}
/// <summary>Verifies that an uncooperative broker still has its child killed at the deadline and keeps the lifetime until the broker completes.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task UncooperativeBroker_StillKillsChildAtDeadline_AndKeepsLifetimeUntilBrokerCompletes()
{
@@ -63,6 +67,8 @@ public class SandboxWorkerIsolationTests
child?.Dispose();
}
/// <summary>Verifies that a non-cooperative worker is terminated and its capacity can be reused.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task NoncooperativeWorker_IsTerminated_AndCapacityCanBeReused()
{
@@ -6,7 +6,7 @@ using ZB.MOM.WW.ScadaBridge.TemplateEngine;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.ScriptAnalysis;
/// <summary>
/// Regression tests for CentralUI-013. <c>ResolveCalledShape</c> resolved shared
/// Regression tests for sync-over-async shared-script resolution. <c>ResolveCalledShape</c> resolved shared
/// script shapes with <c>_sharedScripts.GetShapesAsync().GetAwaiter().GetResult()</c>
/// — a sync-over-async block on a request thread that risks thread-pool
/// starvation and deadlock. <c>Hover</c> and <c>SignatureHelp</c> were synchronous
@@ -20,6 +20,7 @@ public class ScriptAnalysisAsyncResolveTests
private readonly IServiceProvider _services = Substitute.For<IServiceProvider>();
private readonly ScriptAnalysisService _svc;
/// <summary>Initializes the test class and its script analysis service.</summary>
public ScriptAnalysisAsyncResolveTests()
{
_catalog.GetShapesAsync().Returns(Array.Empty<ScriptShape>());
@@ -29,6 +30,8 @@ public class ScriptAnalysisAsyncResolveTests
private static ScriptShape Shape(string name, params ParameterShape[] ps) => new(name, ps, null);
private static ParameterShape Param(string name, string type) => new(name, type, true);
/// <summary>Verifies that hover on a shared-call name awaits the catalog and resolves the call shape.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task HoverAsync_OnSharedCallName_AwaitsCatalog_AndResolvesShape()
{
@@ -53,6 +56,8 @@ public class ScriptAnalysisAsyncResolveTests
Assert.Contains("Aggregate", resp.Markdown);
}
/// <summary>Verifies that signature help inside a shared call awaits the catalog and resolves the parameters.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task SignatureHelpAsync_InsideSharedCall_AwaitsCatalog_AndResolvesParameters()
{
@@ -75,6 +80,7 @@ public class ScriptAnalysisAsyncResolveTests
Assert.Equal(2, resp.Parameters!.Count);
}
/// <summary>Verifies that hover and signature help are asynchronous rather than sync-over-async.</summary>
[Fact]
public void HoverAndSignatureHelp_AreAsync_NotSyncOverAsync()
{
@@ -18,6 +18,7 @@ public class ScriptAnalysisServiceTests
private static ParameterShape Param(string name, string type = "String", bool required = true) =>
new(name, type, required);
/// <summary>Initializes the service under test with a substitute shared-script catalog.</summary>
public ScriptAnalysisServiceTests()
{
_catalog.GetShapesAsync().Returns(Array.Empty<ScriptShape>());
@@ -26,6 +27,7 @@ public class ScriptAnalysisServiceTests
// ── Diagnose ──────────────────────────────────────────────────────────
/// <summary>Verifies that empty code produces no diagnostic markers.</summary>
[Fact]
public void EmptyCode_NoMarkers()
{
@@ -33,6 +35,7 @@ public class ScriptAnalysisServiceTests
Assert.Empty(resp.Markers);
}
/// <summary>Verifies that a clean script produces no diagnostic markers.</summary>
[Fact]
public void CleanScript_NoMarkers()
{
@@ -40,6 +43,7 @@ public class ScriptAnalysisServiceTests
Assert.Empty(resp.Markers);
}
/// <summary>Verifies that a missing semicolon is reported as a Roslyn diagnostic.</summary>
[Fact]
public void MissingSemicolon_ReportsRoslynDiagnostic()
{
@@ -47,6 +51,7 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Markers, m => m.Code.StartsWith("CS"));
}
/// <summary>Verifies that a forbidden using directive raises diagnostic SCADA001.</summary>
[Fact]
public void ForbiddenUsingDirective_RaisesSCADA001()
{
@@ -54,19 +59,22 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Markers, m => m.Code == "SCADA001" && m.Message.Contains("System.IO"));
}
/// <summary>Verifies that every banned namespace raises a forbidden-using diagnostic.</summary>
/// <param name="ns">The banned namespace to import.</param>
[Theory]
[InlineData("System.Reflection")]
[InlineData("System.Net")]
[InlineData("System.Threading")]
public void ForbiddenUsing_AllBannedNamespaces(string ns)
{
// M3.5: the editor deny-list is sourced from the shared ScriptTrustPolicy.
// The editor deny-list is sourced from the shared ScriptTrustPolicy.
// System.Threading is now a forbidden root (only Tasks +
// CancellationToken(Source) are excepted).
var resp = _svc.Diagnose(new DiagnoseRequest($"using {ns};"));
Assert.Contains(resp.Markers, m => m.Code == "SCADA001" && m.Message.Contains(ns));
}
/// <summary>Verifies that forbidden type usage is detected through the semantic model.</summary>
[Fact]
public void ForbiddenTypeUsage_ResolvesViaSemanticModel()
{
@@ -75,16 +83,18 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Markers, m => m.Code == "SCADA002" && m.Message.Contains("File"));
}
/// <summary>Verifies that the System.Diagnostics namespace is no longer forbidden wholesale.</summary>
[Fact]
public void SystemDiagnosticsNamespace_NoLongerWholesaleForbidden()
{
// M3.5: the unified policy forbids only System.Diagnostics.Process, not the
// The unified policy forbids only System.Diagnostics.Process, not the
// whole System.Diagnostics namespace — so a bare `using System.Diagnostics;`
// is no longer flagged (Stopwatch & friends are allowed).
var resp = _svc.Diagnose(new DiagnoseRequest("using System.Diagnostics;"));
Assert.DoesNotContain(resp.Markers, m => m.Code == "SCADA001");
}
/// <summary>Verifies that Stopwatch usage is allowed under the unified trust policy.</summary>
[Fact]
public void StopwatchUsage_IsAllowedUnderUnifiedPolicy()
{
@@ -95,6 +105,7 @@ public class ScriptAnalysisServiceTests
Assert.DoesNotContain(resp.Markers, m => m.Code is "SCADA001" or "SCADA002");
}
/// <summary>Verifies that Process usage is forbidden under the unified trust policy.</summary>
[Fact]
public void ProcessUsage_IsForbiddenUnderUnifiedPolicy()
{
@@ -105,6 +116,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Markers, m => m.Code == "SCADA002" && m.Message.Contains("Process"));
}
/// <summary>Verifies that threading primitives are flagged under the unified trust policy.</summary>
/// <param name="typeName">The threading type used in the script.</param>
[Theory]
[InlineData("Monitor")]
[InlineData("Interlocked")]
@@ -120,6 +133,7 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Markers, m => m.Code is "SCADA001" or "SCADA002");
}
/// <summary>Verifies that CancellationToken usage stays allowed under the unified trust policy.</summary>
[Fact]
public void CancellationToken_StaysAllowed_UnderUnifiedPolicy()
{
@@ -130,6 +144,7 @@ public class ScriptAnalysisServiceTests
Assert.DoesNotContain(resp.Markers, m => m.Code is "SCADA001" or "SCADA002");
}
/// <summary>Verifies that a user identifier named File does not trigger a false positive.</summary>
[Fact]
public void UserIdentifierNamedFile_DoesNotFalsePositive()
{
@@ -138,6 +153,7 @@ public class ScriptAnalysisServiceTests
Assert.DoesNotContain(resp.Markers, m => m.Code == "SCADA002");
}
/// <summary>Verifies that a user identifier named Thread does not trigger a false positive.</summary>
[Fact]
public void UserIdentifierNamedThread_DoesNotFalsePositive()
{
@@ -146,6 +162,7 @@ public class ScriptAnalysisServiceTests
Assert.DoesNotContain(resp.Markers, m => m.Code == "SCADA002");
}
/// <summary>Verifies that diagnostics are cached so a second call skips recompilation.</summary>
[Fact]
public void DiagnosticsAreCached_SecondCallSkipsRecompile()
{
@@ -155,6 +172,7 @@ public class ScriptAnalysisServiceTests
Assert.Same(first, second);
}
/// <summary>Verifies that different code gets different cache entries.</summary>
[Fact]
public void DifferentCode_GetsDifferentCacheEntries()
{
@@ -163,6 +181,7 @@ public class ScriptAnalysisServiceTests
Assert.NotSame(a, b);
}
/// <summary>Verifies that an unknown parameter key raises diagnostic SCADA003.</summary>
[Fact]
public void UnknownParameterKey_RaisesSCADA003()
{
@@ -172,6 +191,7 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Markers, m => m.Code == "SCADA003" && m.Message.Contains("'typo'"));
}
/// <summary>Verifies that a declared parameter key produces no marker.</summary>
[Fact]
public void DeclaredParameterKey_NoMarker()
{
@@ -183,6 +203,8 @@ public class ScriptAnalysisServiceTests
// ── Completions ───────────────────────────────────────────────────────
/// <summary>Verifies that completion inside a Parameters string literal returns the declared parameter names.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ParametersStringLiteral_ReturnsDeclaredParameterNames()
{
@@ -198,6 +220,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Items, i => i.Label == "temperature");
}
/// <summary>Verifies that completion inside a CallScript string literal returns sibling script names with a snippet.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CallScriptStringLiteral_ReturnsSiblingNamesWithSnippet()
{
@@ -218,6 +242,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains("sibling script", item.Detail);
}
/// <summary>Verifies that completion inside a CallShared string literal resolves shared scripts through the catalog with their shapes.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CallSharedStringLiteral_ResolvesViaCatalogWithShapes()
{
@@ -243,6 +269,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains("shared script", greet.Detail);
}
/// <summary>Verifies that general completion returns the in-scope symbols.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task GeneralCompletion_ReturnsInScopeSymbols()
{
@@ -258,6 +286,8 @@ public class ScriptAnalysisServiceTests
// ── Hover ─────────────────────────────────────────────────────────────
/// <summary>Verifies that hovering over a sibling script name returns its signature.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Hover_OnSiblingName_ReturnsSignature()
{
@@ -273,6 +303,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains("Calc(x: Integer, y: Float): void", resp.Markdown);
}
/// <summary>Verifies that hovering over an unrelated token returns null.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Hover_OnUnrelatedToken_ReturnsNull()
{
@@ -285,6 +317,8 @@ public class ScriptAnalysisServiceTests
// ── Signature help ────────────────────────────────────────────────────
/// <summary>Verifies that signature help inside a CallScript call returns the parameter strip.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task SignatureHelp_InsideCallScript_ReturnsParameterStrip()
{
@@ -301,6 +335,8 @@ public class ScriptAnalysisServiceTests
Assert.Equal(1, resp.ActiveParameter);
}
/// <summary>Verifies that signature help outside a call returns null.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task SignatureHelp_OutsideCall_ReturnsNull()
{
@@ -313,6 +349,7 @@ public class ScriptAnalysisServiceTests
// ── Format ────────────────────────────────────────────────────────────
/// <summary>Verifies that formatting scrambled code returns pretty-printed code.</summary>
[Fact]
public void Format_ScrambledCode_ReturnsPrettyPrinted()
{
@@ -322,6 +359,7 @@ public class ScriptAnalysisServiceTests
Assert.NotEqual("if(x){return 1;}else{return 2;}", resp.Code);
}
/// <summary>Verifies that formatting empty code returns an empty result.</summary>
[Fact]
public void Format_EmptyCode_ReturnsEmpty()
{
@@ -334,6 +372,8 @@ public class ScriptAnalysisServiceTests
private static CompositionContext Comp(string name, AttributeShape[]? attrs = null, ScriptShape[]? scripts = null)
=> new(name, attrs ?? Array.Empty<AttributeShape>(), scripts ?? Array.Empty<ScriptShape>());
/// <summary>Verifies that completion inside a self attribute literal returns the self attribute names.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task SelfAttribute_Literal_ReturnsSelfAttributeNames()
{
@@ -347,6 +387,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Items, i => i.Label == "Setpoint" && i.Detail.Contains("Float"));
}
/// <summary>Verifies that completion inside a child attribute literal returns the child attribute names.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ChildAttribute_Literal_ReturnsChildAttributeNames()
{
@@ -360,6 +402,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Items, i => i.Label == "Humidity");
}
/// <summary>Verifies that completion inside a parent attribute literal returns the parent attribute names.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ParentAttribute_Literal_ReturnsParentAttributeNames()
{
@@ -372,6 +416,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Items, i => i.Label == "SpeedRPM");
}
/// <summary>Verifies that completion inside a children literal returns the composition names.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ChildrenLiteral_ReturnsCompositionNames()
{
@@ -385,6 +431,7 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Items, i => i.Label == "PressureSensor");
}
/// <summary>Verifies that an unknown self attribute raises diagnostic SCADA006.</summary>
[Fact]
public void UnknownSelfAttribute_RaisesSCADA006()
{
@@ -394,6 +441,7 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Markers, m => m.Code == "SCADA006" && m.Message.Contains("Typo"));
}
/// <summary>Verifies that a known self attribute produces no marker.</summary>
[Fact]
public void KnownSelfAttribute_NoMarker()
{
@@ -403,6 +451,7 @@ public class ScriptAnalysisServiceTests
Assert.DoesNotContain(resp.Markers, m => m.Code == "SCADA006");
}
/// <summary>Verifies that an unknown child attribute raises diagnostic SCADA006.</summary>
[Fact]
public void UnknownChildAttribute_RaisesSCADA006()
{
@@ -412,6 +461,7 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Markers, m => m.Code == "SCADA006" && m.Message.Contains("TempSensor"));
}
/// <summary>Verifies that an unknown composition raises diagnostic SCADA007.</summary>
[Fact]
public void UnknownComposition_RaisesSCADA007()
{
@@ -421,6 +471,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Markers, m => m.Code == "SCADA007" && m.Message.Contains("Unknown"));
}
/// <summary>Verifies that completion inside a children CallScript call returns the child scripts.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ChildrenCallScript_ReturnsChildScripts()
{
@@ -438,6 +490,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains("${1:count}", sample.InsertText);
}
/// <summary>Verifies that completion inside a parent CallScript call returns the parent scripts.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ParentCallScript_ReturnsParentScripts()
{
@@ -452,6 +506,8 @@ public class ScriptAnalysisServiceTests
// ── Hover on Parameters["name"] ───────────────────────────────────────
/// <summary>Verifies that hovering over a Parameters key shows the declared type.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Hover_OnParametersKey_ShowsDeclaredType()
{
@@ -465,8 +521,9 @@ public class ScriptAnalysisServiceTests
Assert.Contains("String", resp.Markdown);
}
// ── CentralUI-001: trust-model gate before sandbox execution ──────────
// ── Trust-model gate before sandbox execution ──────────
/// <summary>Verifies that a fully qualified forbidden call raises diagnostic SCADA002.</summary>
[Fact]
public void Diagnose_FullyQualifiedForbiddenCall_RaisesSCADA002()
{
@@ -478,10 +535,12 @@ public class ScriptAnalysisServiceTests
Assert.Contains(resp.Markers, m => m.Code == "SCADA002");
}
/// <summary>Verifies that a fully qualified forbidden API is blocked before the sandbox executes the script.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RunInSandbox_FullyQualifiedForbiddenApi_IsBlockedBeforeExecution()
{
// Regression test for CentralUI-001. RunInSandboxAsync used to execute any
// Regression test: RunInSandboxAsync used to execute any
// script that compiled, with no trust-model enforcement — so fully-qualified
// forbidden API code ran in the central host process. The fix gates execution
// on the forbidden-API analysis.
@@ -499,6 +558,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains(result.Markers!, m => m.Code is "SCADA001" or "SCADA002");
}
/// <summary>Verifies that a forbidden using directive is blocked before the sandbox executes the script.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RunInSandbox_ForbiddenUsingDirective_IsBlockedBeforeExecution()
{
@@ -513,6 +574,8 @@ public class ScriptAnalysisServiceTests
Assert.Equal(SandboxErrorKind.CompileError, result.ErrorKind);
}
/// <summary>Verifies that a clean script still runs in the sandbox.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RunInSandbox_CleanScript_StillRuns()
{
@@ -525,6 +588,7 @@ public class ScriptAnalysisServiceTests
Assert.Equal("42", result.ReturnValueJson);
}
/// <summary>Verifies that a script using the notification outbox shape diagnoses clean.</summary>
[Fact]
public void NotifyOutboxShape_DiagnosesClean()
{
@@ -542,6 +606,8 @@ public class ScriptAnalysisServiceTests
Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("SCADA"));
}
/// <summary>Verifies that a script using the notification outbox shape still runs in the sandbox.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RunInSandbox_NotifyOutboxShape_StillRuns()
{
@@ -561,6 +627,8 @@ public class ScriptAnalysisServiceTests
Assert.Equal("\"Unknown\"", result.ReturnValueJson);
}
/// <summary>Verifies that the sandbox captures console output.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RunInSandbox_CapturesConsoleOutput()
{
@@ -575,6 +643,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains("hello-sandbox", result.ConsoleOutput);
}
/// <summary>Verifies that the Log global writes to the sandbox console output.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RunInSandbox_LogGlobal_WritesToConsoleOutput()
{
@@ -592,6 +662,8 @@ public class ScriptAnalysisServiceTests
Assert.Contains("[info] hello", result.ConsoleOutput);
}
/// <summary>Verifies that the Log global in an inbound script writes to the sandbox console output.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RunInSandbox_InboundScript_LogGlobal_WritesToConsoleOutput()
{
@@ -609,10 +681,12 @@ public class ScriptAnalysisServiceTests
Assert.Contains("[info] hello", result.ConsoleOutput);
}
/// <summary>Verifies that concurrent sandbox runs do not cross-contaminate each other's console output.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RunInSandbox_ConcurrentRuns_DoNotCrossContaminateConsoleOutput()
{
// Regression test for CentralUI-003. RunInSandboxAsync used to redirect the
// Regression test: RunInSandboxAsync used to redirect the
// process-global Console.Out/Error to a per-call StringWriter. While one run
// is mid-flight, any concurrent run's `finally` restores Console.Out to the
// ORIGINAL writer — so the long run loses every Console.WriteLine it makes
@@ -682,6 +756,7 @@ public class ScriptAnalysisServiceTests
// ── Inbound-script analysis surface ──────────────────────────────────
/// <summary>Verifies that an inbound script using WaitForAttribute diagnoses clean.</summary>
[Fact]
public void InboundScript_WaitForAttribute_DiagnosesClean()
{
@@ -696,6 +771,7 @@ public class ScriptAnalysisServiceTests
Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("SCADA"));
}
/// <summary>Verifies that an inbound script using Log diagnoses clean.</summary>
[Fact]
public void InboundScript_Log_DiagnosesClean()
{
@@ -714,6 +790,7 @@ public class ScriptAnalysisServiceTests
Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("SCADA"));
}
/// <summary>Verifies that a site script using Log diagnoses clean.</summary>
[Fact]
public void SiteScript_Log_DiagnosesClean()
{
@@ -730,10 +807,11 @@ public class ScriptAnalysisServiceTests
Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("SCADA"));
}
/// <summary>Verifies that an inbound script using the Database helper diagnoses clean.</summary>
[Fact]
public void InboundScript_Database_DiagnosesClean()
{
// InboundAPI-026/030: the scoped Database helper is a shipped inbound-script
// The scoped Database helper is a shipped inbound-script
// capability. The editor must not flag its async read/write API as an error —
// the InboundScriptHost mirror must expose QuerySingleAsync/QueryAsync/ExecuteAsync
// with signatures matching the runtime helper so Roslyn resolves them.
@@ -747,16 +825,17 @@ public class ScriptAnalysisServiceTests
Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("SCADA"));
}
// ── Instance-script batch/wait surface (follow-up #7) ─────────────────
// ── Instance-script batch/wait surface ─────────────────
/// <summary>Verifies that an instance script using the batch-write and wait helpers diagnoses clean.</summary>
[Fact]
public void InstanceScript_BatchAndWaitHelpers_DiagnoseClean()
{
// Follow-up #7: SandboxAttributeAccessor was missing WriteBatchAndWaitAsync /
// SandboxAttributeAccessor was missing WriteBatchAndWaitAsync /
// WaitAsync / WaitForAsync, so the editor false-flagged valid instance scripts
// with CS1061 even though `template validate` and the deploy gate accept them.
// The sandbox surface must mirror the runtime AttributeAccessor member-for-member.
// Gitea #36: also covers the reset-aware WriteBatchAndWaitAsync overload
// Also covers the reset-aware WriteBatchAndWaitAsync overload
// (writes+waits a reset key/value before the batch) with and without the
// optional resetTimeout, returning a HandshakeResult.
var code =
@@ -774,6 +853,7 @@ public class ScriptAnalysisServiceTests
Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("SCADA"));
}
/// <summary>Verifies that a child instance script using WriteBatchAndWait diagnoses clean.</summary>
[Fact]
public void ChildInstanceScript_WriteBatchAndWait_DiagnoseClean()
{
@@ -796,6 +876,7 @@ public class ScriptAnalysisServiceTests
// ── Alarms read surface (MES alarm-status API §5.2) ───────────────────
/// <summary>Verifies that an instance script using Alarms.CurrentAsync diagnoses clean.</summary>
[Fact]
public void InstanceScript_AlarmsCurrentAsync_DiagnoseClean()
{