using System; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware; using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Backend; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests { /// /// PR C.2 — pins the env-var contract that gates whether the sidecar boots an /// alarm-event writer. Default-on (when the historian itself is enabled) so a /// fresh deploy picks up the writer without a service-config edit; explicit /// false opts a read-only deployment out. /// [Trait("Category", "Unit")] public sealed class ProgramAlarmWriterTests { [Fact] public void BuildAlarmWriter_returns_writer_when_env_unset() { using var _ = ScopedEnv("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED", null); var writer = Program.BuildAlarmWriter(); writer.ShouldNotBeNull(); writer.ShouldBeOfType(); } [Theory] [InlineData("true")] [InlineData("True")] [InlineData("TRUE")] public void BuildAlarmWriter_returns_writer_when_env_explicitly_true(string value) { using var _ = ScopedEnv("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED", value); var writer = Program.BuildAlarmWriter(); writer.ShouldNotBeNull(); } [Theory] [InlineData("false")] [InlineData("False")] [InlineData("FALSE")] public void BuildAlarmWriter_returns_null_when_env_false(string value) { using var _ = ScopedEnv("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED", value); var writer = Program.BuildAlarmWriter(); writer.ShouldBeNull(); } [Fact] public void BuildAlarmWriter_treats_unrecognized_value_as_enabled() { // Anything other than the literal "false" (case-insensitive) keeps the writer // wired — fail-open under accidental misconfiguration so an alarm-write deploy // doesn't silently lose alarms because of a typo. using var _ = ScopedEnv("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED", "yes"); var writer = Program.BuildAlarmWriter(); writer.ShouldNotBeNull(); } private static IDisposable ScopedEnv(string name, string? value) { var prior = Environment.GetEnvironmentVariable(name); Environment.SetEnvironmentVariable(name, value); return new DisposableAction(() => Environment.SetEnvironmentVariable(name, prior)); } private sealed class DisposableAction : IDisposable { private readonly Action _action; public DisposableAction(Action action) { _action = action; } public void Dispose() => _action(); } } }