using System.Text.Json; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Host.Configuration; namespace ZB.MOM.WW.OtOpcUa.Host.Tests.Configuration; /// /// #678.6 — the per-role appsettings overlay name is built from plain roles only. A cluster-* role in the /// name produced a file that never exists, so no overlay ever loaded on a node with a cluster role. /// public sealed class RoleOverlayTests { [Fact] public void A_driver_site_node_loads_the_driver_overlay() => RoleOverlay.Suffix(["driver", "cluster-SITE-A"]).ShouldBe("driver"); [Fact] public void A_central_node_loads_the_admin_driver_overlay() => RoleOverlay.Suffix(["admin", "driver", "cluster-MAIN"]).ShouldBe("admin-driver"); [Fact] public void Plain_roles_are_ordinal_sorted_regardless_of_input_order() => RoleOverlay.Suffix(["driver", "cluster-MAIN", "admin"]).ShouldBe("admin-driver"); [Fact] public void Roles_without_a_cluster_role_keep_the_existing_names() { RoleOverlay.Suffix(["admin"]).ShouldBe("admin"); RoleOverlay.Suffix(["driver"]).ShouldBe("driver"); RoleOverlay.Suffix(["admin", "driver"]).ShouldBe("admin-driver"); } [Fact] public void A_cluster_role_prefix_is_matched_case_insensitively() => RoleOverlay.Suffix(["driver", "CLUSTER-SITE-A"]).ShouldBe("driver"); [Fact] public void No_plain_role_means_no_overlay() { RoleOverlay.Suffix([]).ShouldBeNull(); RoleOverlay.Suffix(["cluster-SITE-A"]).ShouldBeNull(); } // ---- The shipped overlays: activating them on cluster-role nodes must not change production semantics. ---- private static string HostDir() { for (var dir = new DirectoryInfo(AppContext.BaseDirectory); dir is not null; dir = dir.Parent) { var candidate = Path.Combine(dir.FullName, "src", "Server", "ZB.MOM.WW.OtOpcUa.Host"); if (File.Exists(Path.Combine(candidate, "appsettings.json"))) return candidate; } throw new InvalidOperationException("Host project directory not found above " + AppContext.BaseDirectory); } [Theory] [InlineData("admin")] [InlineData("driver")] [InlineData("admin-driver")] public void Shipped_overlays_carry_only_the_audited_keys(string suffix) { using var doc = JsonDocument.Parse(File.ReadAllText(Path.Combine(HostDir(), $"appsettings.{suffix}.json"))); var root = doc.RootElement; // Only Serilog + Security:Ldap, and in Security:Ldap only the two audited keys. root.EnumerateObject().Select(p => p.Name).Order().ShouldBe(["Security", "Serilog"]); var ldap = root.GetProperty("Security").GetProperty("Ldap"); ldap.EnumerateObject().Select(p => p.Name).Order().ShouldBe(["DevStubMode", "Transport"]); ldap.GetProperty("DevStubMode").GetBoolean().ShouldBeFalse(); // No Debug-level (or lower) category override: activating an overlay must not raise log volume on a node. foreach (var o in root.GetProperty("Serilog").GetProperty("MinimumLevel").GetProperty("Override").EnumerateObject()) o.Value.GetString().ShouldNotBeOneOf("Debug", "Verbose"); root.GetProperty("Serilog").GetProperty("MinimumLevel").GetProperty("Default").GetString().ShouldBe("Information"); } }