using Microsoft.Extensions.Options; namespace ScadaLink.HealthMonitoring.Tests; /// /// HealthMonitoring-014 regression: intervals /// are fed straight into new PeriodicTimer(...), which throws /// for a zero/negative period. A /// misconfigured appsettings.json must be rejected by an /// with a clear, key-naming message /// rather than crashing the hosted service with an opaque exception. /// public class HealthMonitoringOptionsValidatorTests { private static ValidateOptionsResult Validate(HealthMonitoringOptions options) => new HealthMonitoringOptionsValidator().Validate(Options.DefaultName, options); [Fact] public void DefaultOptions_AreValid() { var result = Validate(new HealthMonitoringOptions()); Assert.True(result.Succeeded, result.FailureMessage); } [Fact] public void ZeroReportInterval_IsRejected() { var result = Validate(new HealthMonitoringOptions { ReportInterval = TimeSpan.Zero }); Assert.True(result.Failed); Assert.Contains("ReportInterval", result.FailureMessage); } [Fact] public void NegativeReportInterval_IsRejected() { var result = Validate(new HealthMonitoringOptions { ReportInterval = TimeSpan.FromSeconds(-1) }); Assert.True(result.Failed); Assert.Contains("ReportInterval", result.FailureMessage); } [Fact] public void ZeroOfflineTimeout_IsRejected() { var result = Validate(new HealthMonitoringOptions { OfflineTimeout = TimeSpan.Zero }); Assert.True(result.Failed); Assert.Contains("OfflineTimeout", result.FailureMessage); } [Fact] public void ZeroCentralOfflineTimeout_IsRejected() { var result = Validate(new HealthMonitoringOptions { CentralOfflineTimeout = TimeSpan.Zero }); Assert.True(result.Failed); Assert.Contains("CentralOfflineTimeout", result.FailureMessage); } [Fact] public void CentralOfflineTimeout_ShorterThanOfflineTimeout_IsRejected() { var result = Validate(new HealthMonitoringOptions { OfflineTimeout = TimeSpan.FromSeconds(60), CentralOfflineTimeout = TimeSpan.FromSeconds(30) }); Assert.True(result.Failed); Assert.Contains("CentralOfflineTimeout", result.FailureMessage); } }