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:
@@ -3,7 +3,7 @@ using Microsoft.Extensions.Configuration;
|
||||
namespace ZB.MOM.WW.ScadaBridge.Host.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// WP-11: Tests for StartupValidator configuration validation.
|
||||
/// Tests for StartupValidator configuration validation.
|
||||
/// </summary>
|
||||
public class StartupValidatorTests
|
||||
{
|
||||
@@ -38,18 +38,20 @@ public class StartupValidatorTests
|
||||
["ScadaBridge:Database:SiteDbPath"] = "./data/scadabridge.db",
|
||||
["ScadaBridge:Cluster:SeedNodes:0"] = "akka.tcp://scadabridge@site-a-node1:8082",
|
||||
["ScadaBridge:Cluster:SeedNodes:1"] = "akka.tcp://scadabridge@site-a-node2:8082",
|
||||
// T0.3: the gRPC control plane is fail-closed, so a Site node without a preshared
|
||||
// The gRPC control plane is fail-closed, so a Site node without a preshared
|
||||
// key serves nothing while still looking healthy. Required at boot for that reason.
|
||||
["ScadaBridge:Communication:GrpcPsk"] = "test-site-control-plane-key",
|
||||
// Phase 4: gRPC (CentralControlService) is the only site→central transport, so a Site
|
||||
// node must list at least one central gRPC endpoint to dial (no Akka fallback remains).
|
||||
["ScadaBridge:Communication:CentralGrpcEndpoints:0"] = "http://central-a:8083",
|
||||
// arch-review remediation WP1.2: the site hot-path audit writer's SQLite file has no
|
||||
// The site hot-path audit writer's SQLite file has no
|
||||
// default path (mirrors LocalDb:Path) — required so it lands on the mounted data volume,
|
||||
// not the container's ephemeral overlayfs.
|
||||
["AuditLog:SiteWriter:DatabasePath"] = "/app/data/auditlog.db",
|
||||
};
|
||||
|
||||
/// <summary>Verifies that a valid central config passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void ValidCentralConfig_PassesValidation()
|
||||
{
|
||||
@@ -58,6 +60,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a valid site config passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void ValidSiteConfig_PassesValidation()
|
||||
{
|
||||
@@ -66,6 +70,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site without gRPC PSK fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void SiteWithoutGrpcPsk_FailsValidation()
|
||||
{
|
||||
@@ -81,6 +87,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("GrpcPsk", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site with a blank gRPC PSK fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void SiteWithBlankGrpcPsk_FailsValidation()
|
||||
{
|
||||
@@ -94,6 +102,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("GrpcPsk", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a central node without gRPC PSK passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void CentralWithoutGrpcPsk_PassesValidation()
|
||||
{
|
||||
@@ -104,6 +114,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(Record.Exception(() => StartupValidator.Validate(config)));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site without central gRPC endpoints fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void SiteWithoutCentralGrpcEndpoints_FailsValidation()
|
||||
{
|
||||
@@ -118,6 +130,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("CentralGrpcEndpoints", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a central node without central gRPC endpoints passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void CentralWithoutCentralGrpcEndpoints_PassesValidation()
|
||||
{
|
||||
@@ -128,10 +142,12 @@ public class StartupValidatorTests
|
||||
Assert.Null(Record.Exception(() => StartupValidator.Validate(config)));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site without audit log database path fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void SiteWithoutAuditLogDatabasePath_FailsValidation()
|
||||
{
|
||||
// arch-review remediation WP1.2: SqliteAuditWriterOptions.DatabasePath has no default
|
||||
// SqliteAuditWriterOptions.DatabasePath has no default
|
||||
// (mirrors LocalDb:Path). An unset value used to fall back to a bare "auditlog.db"
|
||||
// resolved relative to CWD — on the docker rig, the container's ephemeral overlayfs —
|
||||
// so the node booted, looked healthy, and lost its pending audit backlog on every
|
||||
@@ -144,6 +160,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("DatabasePath", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site with a blank audit log database path fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void SiteWithBlankAuditLogDatabasePath_FailsValidation()
|
||||
{
|
||||
@@ -155,6 +173,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("DatabasePath", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a central node without audit log database path passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void CentralWithoutAuditLogDatabasePath_PassesValidation()
|
||||
{
|
||||
@@ -165,6 +185,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(Record.Exception(() => StartupValidator.Validate(config)));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a missing role fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void MissingRole_FailsValidation()
|
||||
{
|
||||
@@ -176,6 +198,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("Role must be 'Central' or 'Site'", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an invalid role fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void InvalidRole_FailsValidation()
|
||||
{
|
||||
@@ -187,6 +211,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("Role must be 'Central' or 'Site'", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an empty hostname fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void EmptyHostname_FailsValidation()
|
||||
{
|
||||
@@ -198,6 +224,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("NodeHostname is required", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a missing hostname fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void MissingHostname_FailsValidation()
|
||||
{
|
||||
@@ -209,6 +237,9 @@ public class StartupValidatorTests
|
||||
Assert.Contains("NodeHostname is required", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an invalid port fails validation.</summary>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <returns>The result.</returns>
|
||||
[Theory]
|
||||
[InlineData("0")]
|
||||
[InlineData("-1")]
|
||||
@@ -225,6 +256,9 @@ public class StartupValidatorTests
|
||||
Assert.Contains("RemotingPort must be 1-65535", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a valid port passes validation.</summary>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <returns>The result.</returns>
|
||||
[Theory]
|
||||
[InlineData("1")]
|
||||
[InlineData("8081")]
|
||||
@@ -243,6 +277,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site without a site id fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_MissingSiteId_FailsValidation()
|
||||
{
|
||||
@@ -254,6 +290,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("SiteId is required for Site nodes", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a central node missing configuration database fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Central_MissingConfigurationDb_FailsValidation()
|
||||
{
|
||||
@@ -265,14 +303,16 @@ public class StartupValidatorTests
|
||||
Assert.Contains("ConfigurationDb connection string required for Central", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a central node missing machine data database fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Central_MissingMachineDataDb_FailsValidation()
|
||||
{
|
||||
// Reverts Host-008. REQ-HOST-3/REQ-HOST-4 require MachineDataDb to be
|
||||
// The host requirements require MachineDataDb to be
|
||||
// validated at startup for Central nodes, and the shipped docker appsettings
|
||||
// (docker/central-node-a/appsettings.Central.json and central-node-b) carry
|
||||
// the key. The prior Host-008 decision (which removed the Require) is reversed
|
||||
// here (#17, M2.9): a missing MachineDataDb must fail fast with a clear error.
|
||||
// the key. The earlier decision (which removed the Require) is reversed
|
||||
// here: a missing MachineDataDb must fail fast with a clear error.
|
||||
var values = ValidCentralConfig();
|
||||
values.Remove("ScadaBridge:Database:MachineDataDb");
|
||||
var config = BuildConfig(values);
|
||||
@@ -281,10 +321,12 @@ public class StartupValidatorTests
|
||||
Assert.Contains("MachineDataDb connection string required for Central", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a central node missing LDAP server fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Central_MissingLdapServer_FailsValidation()
|
||||
{
|
||||
// Task 1.4: the LDAP server key nests under Security:Ldap now. The pre-host
|
||||
// The LDAP server key nests under Security:Ldap now. The pre-host
|
||||
// preflight validates the nested key and still fails fast for Central.
|
||||
var values = ValidCentralConfig();
|
||||
values.Remove("ScadaBridge:Security:Ldap:Server");
|
||||
@@ -294,6 +336,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("Ldap:Server required for Central", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a central node missing JWT signing key fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Central_MissingJwtSigningKey_FailsValidation()
|
||||
{
|
||||
@@ -305,6 +349,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("JwtSigningKey required for Central", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a central node missing API key pepper fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Central_MissingApiKeyPepper_FailsValidation()
|
||||
{
|
||||
@@ -319,6 +365,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("ApiKeyPepper", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a central node with a short API key pepper fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Central_ShortApiKeyPepper_FailsValidation()
|
||||
{
|
||||
@@ -331,6 +379,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("ApiKeyPepper", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site does not require an API key pepper.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_ApiKeyPepper_NotRequired()
|
||||
{
|
||||
@@ -363,6 +413,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that fewer than two seed nodes fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void FewerThanTwoSeedNodes_FailsValidation()
|
||||
{
|
||||
@@ -374,6 +426,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("SeedNodes must have at least 2 entries", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that having no seed nodes fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void NoSeedNodes_FailsValidation()
|
||||
{
|
||||
@@ -386,6 +440,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("SeedNodes must have at least 2 entries", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a peer-first seed order fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void PeerFirstSeedOrder_FailsValidation()
|
||||
{
|
||||
@@ -403,6 +459,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("must list this node itself first", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a self-first seed order on a site node passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void SelfFirstSeedOrder_OnASiteNode_PassesValidation()
|
||||
{
|
||||
@@ -415,6 +473,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the self-first seed check matches on host and port, not just host.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void SelfFirstSeed_MatchedOnHostAndPort_NotJustHost()
|
||||
{
|
||||
@@ -432,6 +492,9 @@ public class StartupValidatorTests
|
||||
Assert.Contains("must list this node itself first", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site with an invalid gRPC port fails validation.</summary>
|
||||
/// <param name="grpcPort">The grpc port.</param>
|
||||
/// <returns>The result.</returns>
|
||||
[Theory]
|
||||
[InlineData("0")]
|
||||
[InlineData("-1")]
|
||||
@@ -447,6 +510,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("GrpcPort must be 1-65535", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site with a valid gRPC port passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_ValidGrpcPort_PassesValidation()
|
||||
{
|
||||
@@ -458,6 +523,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an invalid gRPC port is not validated on a central node.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Central_InvalidGrpcPort_NotValidated()
|
||||
{
|
||||
@@ -469,10 +536,12 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site seed node on the gRPC port fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_SeedNodeOnGrpcPort_FailsValidation()
|
||||
{
|
||||
// Host-004 regression: a site seed node must reference an Akka remoting
|
||||
// Regression: a site seed node must reference an Akka remoting
|
||||
// endpoint, never the Kestrel HTTP/2 gRPC port. A seed node whose port
|
||||
// equals this node's GrpcPort would make a joining node attempt an
|
||||
// Akka.Remote TCP association against the gRPC listener and fail.
|
||||
@@ -485,6 +554,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("must not target the gRPC port", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site seed node on the metrics port fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_SeedNodeOnMetricsPort_FailsValidation()
|
||||
{
|
||||
@@ -501,6 +572,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("must not target the metrics port", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site seed node on the default metrics port fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_SeedNodeOnDefaultMetricsPort_FailsValidation()
|
||||
{
|
||||
@@ -515,6 +588,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("must not target the metrics port", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site seed node on the default gRPC port fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_SeedNodeOnDefaultGrpcPort_FailsValidation()
|
||||
{
|
||||
@@ -528,6 +603,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("must not target the gRPC port", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that site seed nodes on the remoting port pass validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_SeedNodesOnRemotingPort_PassesValidation()
|
||||
{
|
||||
@@ -542,6 +619,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a central seed node on port 8083 passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Central_SeedNodeOnPort8083_PassesValidation()
|
||||
{
|
||||
@@ -555,10 +634,12 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site gRPC port equal to the remoting port fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_GrpcPortEqualsRemotingPort_FailsValidation()
|
||||
{
|
||||
// Host-007 regression: REQ-HOST-4 requires GrpcPort to differ from
|
||||
// Regression: the host requirements require GrpcPort to differ from
|
||||
// RemotingPort. Identical values cause Kestrel and Akka.Remote to
|
||||
// contend for the same port at runtime.
|
||||
var values = ValidSiteConfig();
|
||||
@@ -570,6 +651,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("GrpcPort must differ from RemotingPort", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site default gRPC port equal to the remoting port fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_DefaultGrpcPortEqualsRemotingPort_FailsValidation()
|
||||
{
|
||||
@@ -583,6 +666,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("GrpcPort must differ from RemotingPort", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site gRPC port that differs from the remoting port passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_GrpcPortDiffersFromRemotingPort_PassesValidation()
|
||||
{
|
||||
@@ -595,6 +680,9 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site with an invalid metrics port fails validation.</summary>
|
||||
/// <param name="metricsPort">The metrics port.</param>
|
||||
/// <returns>The result.</returns>
|
||||
[Theory]
|
||||
[InlineData("0")]
|
||||
[InlineData("-1")]
|
||||
@@ -610,6 +698,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("MetricsPort must be 1-65535", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site with a valid metrics port passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_ValidMetricsPort_PassesValidation()
|
||||
{
|
||||
@@ -621,10 +711,12 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site metrics port equal to the remoting port fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_MetricsPortEqualsRemotingPort_FailsValidation()
|
||||
{
|
||||
// Host-007 regression: the Kestrel metrics (HTTP/1.1) listener port must
|
||||
// Regression: the Kestrel metrics (HTTP/1.1) listener port must
|
||||
// differ from RemotingPort. Identical values cause the metrics listener
|
||||
// and Akka.Remote to contend for the same port at runtime.
|
||||
var values = ValidSiteConfig();
|
||||
@@ -636,10 +728,12 @@ public class StartupValidatorTests
|
||||
Assert.Contains("MetricsPort must differ from RemotingPort", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site metrics port equal to the gRPC port fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_MetricsPortEqualsGrpcPort_FailsValidation()
|
||||
{
|
||||
// Host-007 regression: the metrics listener port must differ from GrpcPort.
|
||||
// Regression: the metrics listener port must differ from GrpcPort.
|
||||
var values = ValidSiteConfig();
|
||||
values["ScadaBridge:Node:GrpcPort"] = "8083";
|
||||
values["ScadaBridge:Node:MetricsPort"] = "8083";
|
||||
@@ -649,6 +743,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("MetricsPort must differ from GrpcPort", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site default metrics port equal to the remoting port fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_DefaultMetricsPortEqualsRemotingPort_FailsValidation()
|
||||
{
|
||||
@@ -668,6 +764,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("MetricsPort must differ from RemotingPort", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site metrics port that differs from the remoting and gRPC ports passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Site_MetricsPortDiffersFromRemotingAndGrpc_PassesValidation()
|
||||
{
|
||||
@@ -681,6 +779,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an invalid metrics port is not validated on a central node.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Central_InvalidMetricsPort_NotValidated()
|
||||
{
|
||||
@@ -694,6 +794,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that collecting warnings warns for a two-seed central node on SQLite.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void CollectWarnings_WarnsForTwoSeedCentralOnSqlite()
|
||||
{
|
||||
@@ -713,6 +815,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("Provider=SqlServer", warning);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that collecting warnings warns when the provider is explicitly SQLite.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void CollectWarnings_WarnsWhenProviderExplicitlySqlite()
|
||||
{
|
||||
@@ -726,6 +830,8 @@ public class StartupValidatorTests
|
||||
Assert.Single(warnings);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that collecting warnings is silent for SQL Server.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void CollectWarnings_SilentForSqlServer()
|
||||
{
|
||||
@@ -739,10 +845,12 @@ public class StartupValidatorTests
|
||||
Assert.Empty(warnings);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a clustered central node on a SQLite secret store produces a warning.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void ClusteredCentral_SqliteSecretStore_Warns()
|
||||
{
|
||||
// Task 5: a clustered central left on the per-node SQLite secret store silently drifts
|
||||
// A clustered central left on the per-node SQLite secret store silently drifts
|
||||
// the moment anyone edits a secret on only one node, then ships an inconsistent set to
|
||||
// sites. ApiKeyStore:Provider is set to SqlServer here to isolate this warning from the
|
||||
// pre-existing one above.
|
||||
@@ -758,6 +866,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("Secrets:Store:Provider=SqlServer", warning);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a clustered central node on a SQL Server secret store produces no warning.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void ClusteredCentral_SqlServerSecretStore_Silent()
|
||||
{
|
||||
@@ -772,7 +882,7 @@ public class StartupValidatorTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task 11: secrets replication (the gRPC hub, and the SQL-Server ReplicatingSecretStore mode
|
||||
/// Secrets replication (the gRPC hub, and the SQL-Server ReplicatingSecretStore mode
|
||||
/// that preceded it) is retired in ScadaBridge — sites receive secrets exclusively via Deploy
|
||||
/// Artifacts, and a replicated site store would publish DeployedSecretsMirror's local deletes
|
||||
/// fleet-wide. Checked on BOTH roles: a Site used to be the gRPC hub's follower half, so the
|
||||
@@ -781,10 +891,11 @@ public class StartupValidatorTests
|
||||
/// because CollectWarnings never runs with this flag true on a config that also passed
|
||||
/// Validate.
|
||||
/// </summary>
|
||||
/// <param name="value">The rejected replication flag value.</param>
|
||||
[Theory]
|
||||
[InlineData("true")]
|
||||
[InlineData("True")]
|
||||
// Review round 1, Minor #3: these are not valid booleans. GetValue<bool> used to throw a raw
|
||||
// These are not valid booleans. GetValue<bool> used to throw a raw
|
||||
// OptionsValidationException-style binder crash on them instead of the designed message; the
|
||||
// key is read as a raw string now, so any non-"false" value is "set" and rejected the same way.
|
||||
[InlineData("yes")]
|
||||
@@ -803,6 +914,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("central uses Secrets:Store:Provider=SqlServer", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that enabling secrets replication is rejected on a site.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void ReplicationEnabled_IsRejected_OnSite()
|
||||
{
|
||||
@@ -816,6 +929,8 @@ public class StartupValidatorTests
|
||||
"Secrets replication (the gRPC hub) is retired in ScadaBridge", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that disabling secrets replication passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void ReplicationDisabled_PassesValidation()
|
||||
{
|
||||
@@ -830,6 +945,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a case-insensitive replication-disabled value passes validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void ReplicationDisabled_CaseInsensitive_PassesValidation()
|
||||
{
|
||||
@@ -843,7 +960,7 @@ public class StartupValidatorTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deliberately stricter than <c>bool</c> parsing (review round 1, Minor #3): only an absent
|
||||
/// Deliberately stricter than <c>bool</c> parsing (review feedback): only an absent
|
||||
/// key or exactly (case-insensitively) <c>"false"</c> is safe. <c>"0"</c> is a legal
|
||||
/// <c>bool.TryParse</c>-adjacent falsy spelling in some conventions but is NOT exempted here —
|
||||
/// a leftover key this malformed should surface the retirement message, not silently pass.
|
||||
@@ -862,8 +979,8 @@ public class StartupValidatorTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Review round 1, Minor #4: a leftover <c>Secrets:Replication</c>/<c>Secrets:GrpcHub</c> key
|
||||
/// from before Task 11 is not itself a boot failure (Validate already rejects Enabled=true
|
||||
/// A leftover <c>Secrets:Replication</c>/<c>Secrets:GrpcHub</c> key
|
||||
/// from before secrets replication was retired is not itself a boot failure (Validate already rejects Enabled=true
|
||||
/// outright), but every OTHER key under either section is now silently ignored — worth a
|
||||
/// Warning so an operator cleans up QA/prod appsettings.
|
||||
/// </summary>
|
||||
@@ -882,6 +999,8 @@ public class StartupValidatorTests
|
||||
&& w.Contains("the secrets hub is retired; remove them", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that collecting warnings warns for a leftover gRPC hub key.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void CollectWarnings_WarnsForLeftoverGrpcHubKey()
|
||||
{
|
||||
@@ -896,6 +1015,8 @@ public class StartupValidatorTests
|
||||
w.Contains("Secrets:Replication / Secrets:GrpcHub settings are ignored", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that collecting warnings warns for leftover keys on a site too.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void CollectWarnings_WarnsForLeftoverKeys_OnSiteToo()
|
||||
{
|
||||
@@ -911,6 +1032,8 @@ public class StartupValidatorTests
|
||||
w.Contains("Secrets:Replication / Secrets:GrpcHub settings are ignored", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that collecting warnings is silent for a bare replication-enabled false value.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void CollectWarnings_SilentForBareReplicationEnabledFalse()
|
||||
{
|
||||
@@ -928,6 +1051,8 @@ public class StartupValidatorTests
|
||||
w.Contains("Secrets:Replication / Secrets:GrpcHub settings are ignored", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that collecting warnings is silent when neither section is present.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void CollectWarnings_SilentWhenNeitherSectionPresent()
|
||||
{
|
||||
@@ -941,6 +1066,8 @@ public class StartupValidatorTests
|
||||
w.Contains("Secrets:Replication / Secrets:GrpcHub settings are ignored", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that collecting warnings is silent for a single-seed central node.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void CollectWarnings_SilentForSingleSeedCentral()
|
||||
{
|
||||
@@ -955,6 +1082,8 @@ public class StartupValidatorTests
|
||||
Assert.Empty(warnings);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that collecting warnings is silent for a site.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void CollectWarnings_SilentForSite()
|
||||
{
|
||||
@@ -965,6 +1094,8 @@ public class StartupValidatorTests
|
||||
Assert.Empty(warnings);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that validation rejects an unknown API key store provider.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Validate_RejectsUnknownApiKeyStoreProvider()
|
||||
{
|
||||
@@ -976,6 +1107,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("ScadaBridge:InboundApi:ApiKeyStore:Provider", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that validation accepts the API key store provider case-insensitively.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Validate_AcceptsProviderCaseInsensitively()
|
||||
{
|
||||
@@ -987,6 +1120,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an unknown secret store provider fails validation.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void UnknownSecretStoreProvider_Fails()
|
||||
{
|
||||
@@ -998,6 +1133,8 @@ public class StartupValidatorTests
|
||||
Assert.Contains("Secrets:Store:Provider", ex.Message);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that validation accepts the secret store provider case-insensitively.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void Validate_AcceptsSecretStoreProviderCaseInsensitively()
|
||||
{
|
||||
@@ -1009,6 +1146,8 @@ public class StartupValidatorTests
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that multiple errors are all reported.</summary>
|
||||
/// <returns>The result.</returns>
|
||||
[Fact]
|
||||
public void MultipleErrors_AllReported()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user