using Akka.Configuration; using ScadaLink.ClusterInfrastructure; using ScadaLink.Host.Actors; namespace ScadaLink.Host.Tests; /// /// Host-006: the Akka HOCON document must be assembled with every interpolated value /// quoted/escaped, so a hostname, seed-node URI or split-brain strategy containing a /// quote, backslash or whitespace cannot corrupt the document or be silently /// misparsed. /// public class HoconBuilderTests { private static ClusterOptions DefaultCluster() => new() { SeedNodes = new List { "akka.tcp://scadalink@localhost:8081", "akka.tcp://scadalink@localhost:8082", }, SplitBrainResolverStrategy = "keep-oldest", MinNrOfMembers = 1, StableAfter = TimeSpan.FromSeconds(15), HeartbeatInterval = TimeSpan.FromSeconds(2), FailureDetectionThreshold = TimeSpan.FromSeconds(10), }; [Fact] public void BuildHocon_PlainValues_ParsesAndPreservesValues() { var node = new NodeOptions { Role = "Central", NodeHostname = "central-node1", RemotingPort = 8081, }; var hocon = AkkaHostedService.BuildHocon( node, DefaultCluster(), new[] { "Central" }, 5, 15); var config = ConfigurationFactory.ParseString(hocon); Assert.Equal("central-node1", config.GetString("akka.remote.dot-netty.tcp.hostname")); Assert.Equal("keep-oldest", config.GetString("akka.cluster.split-brain-resolver.active-strategy")); } [Fact] public void BuildHocon_HostnameWithQuote_DoesNotCorruptDocument() { // A hostname containing a double quote would, with raw interpolation, close // the HOCON string literal early and corrupt every following key. var node = new NodeOptions { Role = "Central", NodeHostname = "evil\"host", RemotingPort = 8081, }; var hocon = AkkaHostedService.BuildHocon( node, DefaultCluster(), new[] { "Central" }, 5, 15); // Must still parse, and the keys after the hostname must remain intact. var config = ConfigurationFactory.ParseString(hocon); Assert.Equal("evil\"host", config.GetString("akka.remote.dot-netty.tcp.hostname")); Assert.Equal(8081, config.GetInt("akka.remote.dot-netty.tcp.port")); Assert.Equal(1, config.GetInt("akka.cluster.min-nr-of-members")); } [Fact] public void BuildHocon_StrategyWithWhitespace_RemainsQuoted() { var cluster = DefaultCluster(); cluster.SplitBrainResolverStrategy = "keep oldest"; var node = new NodeOptions { Role = "Central", NodeHostname = "node1", RemotingPort = 8081, }; var hocon = AkkaHostedService.BuildHocon( node, cluster, new[] { "Central" }, 5, 15); var config = ConfigurationFactory.ParseString(hocon); Assert.Equal("keep oldest", config.GetString("akka.cluster.split-brain-resolver.active-strategy")); } }