From 64ee3166092b785fb562e492b687f894b1f4d8cc Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 21 Mar 2026 11:42:41 -0400 Subject: [PATCH] feat: add GrpcPort config to NodeOptions with startup validation --- src/ScadaLink.Host/NodeOptions.cs | 1 + src/ScadaLink.Host/StartupValidator.cs | 4 ++ src/ScadaLink.Host/appsettings.Site.json | 3 +- .../StartupValidatorTests.cs | 37 +++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/ScadaLink.Host/NodeOptions.cs b/src/ScadaLink.Host/NodeOptions.cs index b6dcf76..3bc85fb 100644 --- a/src/ScadaLink.Host/NodeOptions.cs +++ b/src/ScadaLink.Host/NodeOptions.cs @@ -6,4 +6,5 @@ public class NodeOptions public string NodeHostname { get; set; } = string.Empty; public string? SiteId { get; set; } public int RemotingPort { get; set; } = 8081; + public int GrpcPort { get; set; } = 8083; } diff --git a/src/ScadaLink.Host/StartupValidator.cs b/src/ScadaLink.Host/StartupValidator.cs index a33bc18..da7dfec 100644 --- a/src/ScadaLink.Host/StartupValidator.cs +++ b/src/ScadaLink.Host/StartupValidator.cs @@ -42,6 +42,10 @@ public static class StartupValidator if (role == "Site") { + var grpcPortStr = nodeSection["GrpcPort"]; + if (grpcPortStr != null && (!int.TryParse(grpcPortStr, out var gp) || gp < 1 || gp > 65535)) + errors.Add("ScadaLink:Node:GrpcPort must be 1-65535"); + var dbSection = configuration.GetSection("ScadaLink:Database"); if (string.IsNullOrEmpty(dbSection["SiteDbPath"])) errors.Add("ScadaLink:Database:SiteDbPath required for Site nodes"); diff --git a/src/ScadaLink.Host/appsettings.Site.json b/src/ScadaLink.Host/appsettings.Site.json index 2a8948f..b22b999 100644 --- a/src/ScadaLink.Host/appsettings.Site.json +++ b/src/ScadaLink.Host/appsettings.Site.json @@ -4,7 +4,8 @@ "Role": "Site", "NodeHostname": "localhost", "SiteId": "site-a", - "RemotingPort": 8082 + "RemotingPort": 8082, + "GrpcPort": 8083 }, "Cluster": { "SeedNodes": [ diff --git a/tests/ScadaLink.Host.Tests/StartupValidatorTests.cs b/tests/ScadaLink.Host.Tests/StartupValidatorTests.cs index 137443c..422a301 100644 --- a/tests/ScadaLink.Host.Tests/StartupValidatorTests.cs +++ b/tests/ScadaLink.Host.Tests/StartupValidatorTests.cs @@ -217,6 +217,43 @@ public class StartupValidatorTests Assert.Contains("SeedNodes must have at least 2 entries", ex.Message); } + [Theory] + [InlineData("0")] + [InlineData("-1")] + [InlineData("65536")] + [InlineData("abc")] + public void Site_InvalidGrpcPort_FailsValidation(string grpcPort) + { + var values = ValidSiteConfig(); + values["ScadaLink:Node:GrpcPort"] = grpcPort; + var config = BuildConfig(values); + + var ex = Assert.Throws(() => StartupValidator.Validate(config)); + Assert.Contains("GrpcPort must be 1-65535", ex.Message); + } + + [Fact] + public void Site_ValidGrpcPort_PassesValidation() + { + var values = ValidSiteConfig(); + values["ScadaLink:Node:GrpcPort"] = "8083"; + var config = BuildConfig(values); + + var ex = Record.Exception(() => StartupValidator.Validate(config)); + Assert.Null(ex); + } + + [Fact] + public void Central_InvalidGrpcPort_NotValidated() + { + var values = ValidCentralConfig(); + values["ScadaLink:Node:GrpcPort"] = "0"; + var config = BuildConfig(values); + + var ex = Record.Exception(() => StartupValidator.Validate(config)); + Assert.Null(ex); + } + [Fact] public void MultipleErrors_AllReported() {