test(coverage): close Theme 8 — 13 test-coverage findings, +35 tests

13 well-bounded test-coverage gaps closed across 11 test projects.
Net +35 regression tests; no production code changes except the
SiteEventLogger src reference unchanged (W3 redacted only test code).

Test additions:
- CLI-022: CommandTreeTests pinned-count assertion bumped 14→16 and
  3 InlineData rows added for the audit + bundle command groups.
- Commons-020: new TransportRecordsTests covers BundleManifest /
  ExportSelection / ImportPreview / ImportResolution / ImportResult —
  ctor + System.Text.Json round-trip + record-equality (14 tests).
- CD-024: SPLIT-RANGE failure-continuation now under
  EnsureLookahead_SecondSplitThrows_LoopAborts_FirstBoundaryStillCommitted
  (Skippable MS-SQL fixture); production-shape rowversion delete
  asserted by DeleteDeploymentRecord_CurrentRowVersion_StubAttachPath_DeleteSucceeds.
- CentralUI-033: new QueryStringDrillInTests with 4 bUnit cases for
  Transport + SiteCalls drill-in / query-string handling.
- DM-024: probe actors (ReconcileProbeActor, SerializationProbeActor,
  ArtifactProbeActor) refactored from static fields to per-test instances
  (Interlocked on counter) — all 31 callers updated; no production
  changes required.
- HM-022: real-time PeriodicTimer test flake fixed by replacing
  fixed-budget Task.Delay with a RunLoopUntil poll-until-condition
  helper (5s/25ms). Production loop untouched.
- InboundAPI-023: new EndpointExtensionsTests covers the
  POST /api/{methodName} composition wiring via TestServer (7 cases:
  happy path, missing key 401, unknown method 403, invalid JSON 400,
  missing param 400, script-throws 500 sanitised, AuditActorItemKey
  stash invariant).
- MgmtSvc-021: 6 new ManagementActorTests cover the Transport bundle
  handlers (role gate for Export/Preview/Import, unknown-name
  ManagementCommandException, blocker-rejection, dedupe last-write-wins).
- SCA-006: SiteCallQueryRequest_StuckOnly_CursorAtNonStuckBoundary_SkipsToNextStuckRow
  pins the missing boundary case.
- SEL-023: stress-test `bool stop` promoted to `volatile bool` for
  cross-thread visibility under release/JIT.

Verify-only resolutions:
- NS-024: closed by NS-019 (commit ac96b83 deletion of
  NotificationDeliveryService + its test file). No edits needed.
- NotifOutbox-008: FallbackMaxRetries/FallbackRetryDelay are private
  forward-compat constants returned only when no SMTP-config row exists
  (in which case EmailNotificationDeliveryAdapter returns Permanent,
  bypassing the values entirely). Marked Resolved with note.
- Transport-010: Overwrite child-collection sync covered by the T-001/
  T-002 tests added in commit e3ca9af; per-IP throttle by
  BundleUnlockRateLimiterTests; failed-session retention by
  BundleSessionStoreTests; T-009 closed structurally via AsyncLocal.
  Marked Resolved by reference.

Build clean; all 11 affected test suites green. README regenerated:
33 open (was 46).
This commit is contained in:
Joseph Doherty
2026-05-28 08:21:03 -04:00
parent 46cb6965ac
commit d190345ef0
26 changed files with 1725 additions and 155 deletions
+49 -1
View File
@@ -18,6 +18,10 @@ public class CommandTreeTests
private static readonly Option<string> Password = new("--password") { Recursive = true };
private static readonly Option<string> Format = CliOptions.CreateFormatOption();
// NOTE: this list MUST stay in sync with the rootCommand.Add(...) calls in
// src/ScadaLink.CLI/Program.cs. When a new command group is added (or one is
// removed/renamed), update this array and bump the count assertion in
// AllCommandGroups_Build_WithoutThrowing accordingly.
private static IEnumerable<Command> AllCommandGroups() => new[]
{
TemplateCommands.Build(Url, Format, Username, Password),
@@ -29,11 +33,13 @@ public class CommandTreeTests
NotificationCommands.Build(Url, Format, Username, Password),
SecurityCommands.Build(Url, Format, Username, Password),
AuditLogCommands.Build(Url, Format, Username, Password),
AuditCommands.Build(Url, Format, Username, Password),
HealthCommands.Build(Url, Format, Username, Password),
DebugCommands.Build(Url, Format, Username, Password),
SharedScriptCommands.Build(Url, Format, Username, Password),
DbConnectionCommands.Build(Url, Format, Username, Password),
ApiMethodCommands.Build(Url, Format, Username, Password),
BundleCommands.Build(Url, Format, Username, Password),
};
private static IEnumerable<Command> LeafCommands(Command command)
@@ -53,10 +59,49 @@ public class CommandTreeTests
public void AllCommandGroups_Build_WithoutThrowing()
{
var groups = AllCommandGroups().ToList();
Assert.Equal(14, groups.Count);
// CLI-022: bump this count whenever a new top-level command group is
// registered in Program.cs. Current registered groups (16):
// template, instance, site, deploy, data-connection, external-system,
// notification, security, audit-config, audit, health, debug,
// shared-script, db-connection, api-method, bundle.
Assert.Equal(16, groups.Count);
Assert.All(groups, g => Assert.False(string.IsNullOrWhiteSpace(g.Name)));
}
[Fact]
public void AllCommandGroups_Contains_AuditAndBundle()
{
// CLI-022: explicit group-presence assertion so the harness does not
// silently drift back to excluding new groups. Use names because that
// is what users actually type at the prompt.
var groupNames = AllCommandGroups().Select(g => g.Name).ToHashSet();
Assert.Contains("audit", groupNames);
Assert.Contains("bundle", groupNames);
}
[Fact]
public void AuditCommandGroup_HasQueryExportAndVerifyChain()
{
// CLI-022: pin the audit sub-command surface so a rename / accidental
// removal of one of these is caught.
var audit = AuditCommands.Build(Url, Format, Username, Password);
var subNames = audit.Subcommands.Select(c => c.Name).ToHashSet();
Assert.Contains("query", subNames);
Assert.Contains("export", subNames);
Assert.Contains("verify-chain", subNames);
}
[Fact]
public void BundleCommandGroup_HasExportPreviewAndImport()
{
// CLI-022: pin the bundle sub-command surface.
var bundle = BundleCommands.Build(Url, Format, Username, Password);
var subNames = bundle.Subcommands.Select(c => c.Name).ToHashSet();
Assert.Contains("export", subNames);
Assert.Contains("preview", subNames);
Assert.Contains("import", subNames);
}
[Fact]
public void EveryLeafCommand_HasAnAction()
{
@@ -93,6 +138,9 @@ public class CommandTreeTests
[InlineData(typeof(DebugSnapshotCommand))]
[InlineData(typeof(MgmtDeployInstanceCommand))]
[InlineData(typeof(QueryAuditLogCommand))]
[InlineData(typeof(ExportBundleCommand))]
[InlineData(typeof(PreviewBundleCommand))]
[InlineData(typeof(ImportBundleCommand))]
public void CommandPayloadTypes_ResolveViaRegistry(Type commandType)
{
// GetCommandName throws ArgumentException for an unregistered type — the CLI