using ZB.MOM.WW.ScadaBridge.KpiHistory; namespace ZB.MOM.WW.ScadaBridge.KpiHistory.Tests; /// /// Unit tests for : the default /// options must validate, and each rule (SampleInterval / RetentionDays / /// PurgeInterval / DefaultMaxSeriesPoints) must reject its out-of-range values. /// public class KpiHistoryOptionsValidatorTests { private static KpiHistoryOptionsValidator NewValidator() => new(); /// Verifies that a zero source timeout fails validation. [Fact] public void Validate_SourceTimeout_Zero_Fails() { var result = NewValidator().Validate(null, new KpiHistoryOptions { SourceTimeout = TimeSpan.Zero }); Assert.True(result.Failed); Assert.Contains("SourceTimeout", result.FailureMessage); } /// Verifies that the default options pass validation. [Fact] public void Validate_Defaults_Pass() { var result = NewValidator().Validate(null, new KpiHistoryOptions()); Assert.True(result.Succeeded); } // --------------------------------------------------------------------- // SampleInterval > TimeSpan.Zero // --------------------------------------------------------------------- /// Verifies that a zero sample interval fails validation. [Fact] public void Validate_SampleInterval_Zero_Fails() { var opts = new KpiHistoryOptions { SampleInterval = TimeSpan.Zero }; var result = NewValidator().Validate(null, opts); Assert.False(result.Succeeded); Assert.Contains( result.Failures!, f => f.Contains(nameof(KpiHistoryOptions.SampleInterval), StringComparison.Ordinal)); } /// Verifies that a negative sample interval fails validation. [Fact] public void Validate_SampleInterval_Negative_Fails() { var opts = new KpiHistoryOptions { SampleInterval = TimeSpan.FromSeconds(-1) }; var result = NewValidator().Validate(null, opts); Assert.False(result.Succeeded); Assert.Contains( result.Failures!, f => f.Contains(nameof(KpiHistoryOptions.SampleInterval), StringComparison.Ordinal)); } // --------------------------------------------------------------------- // RetentionDays in [1, 3650] // --------------------------------------------------------------------- /// Verifies that an in-range retention period passes validation. /// The option value under test. [Theory] [InlineData(1)] // min [InlineData(90)] // default [InlineData(3650)] // max public void Validate_RetentionDays_InRange_Passes(int value) { // Keep RollupRetentionDays coherent (>= RetentionDays) so this test isolates the // RetentionDays range rule and doesn't trip the rollup-retention coherence rule when // RetentionDays is set above the default RollupRetentionDays (365). var opts = new KpiHistoryOptions { RetentionDays = value, RollupRetentionDays = value }; Assert.True(NewValidator().Validate(null, opts).Succeeded); } /// Verifies that an out-of-range retention period fails validation. /// The option value under test. [Theory] [InlineData(0)] [InlineData(3651)] [InlineData(-1)] public void Validate_RetentionDays_OutOfRange_Fails(int value) { var opts = new KpiHistoryOptions { RetentionDays = value }; var result = NewValidator().Validate(null, opts); Assert.False(result.Succeeded); Assert.Contains( result.Failures!, f => f.Contains(nameof(KpiHistoryOptions.RetentionDays), StringComparison.Ordinal)); } // --------------------------------------------------------------------- // PurgeInterval > TimeSpan.Zero // --------------------------------------------------------------------- /// Verifies that a zero purge interval fails validation. [Fact] public void Validate_PurgeInterval_Zero_Fails() { var opts = new KpiHistoryOptions { PurgeInterval = TimeSpan.Zero }; var result = NewValidator().Validate(null, opts); Assert.False(result.Succeeded); Assert.Contains( result.Failures!, f => f.Contains(nameof(KpiHistoryOptions.PurgeInterval), StringComparison.Ordinal)); } /// Verifies that a negative purge interval fails validation. [Fact] public void Validate_PurgeInterval_Negative_Fails() { var opts = new KpiHistoryOptions { PurgeInterval = TimeSpan.FromHours(-1) }; var result = NewValidator().Validate(null, opts); Assert.False(result.Succeeded); Assert.Contains( result.Failures!, f => f.Contains(nameof(KpiHistoryOptions.PurgeInterval), StringComparison.Ordinal)); } // --------------------------------------------------------------------- // DefaultMaxSeriesPoints in [2, 5000] // --------------------------------------------------------------------- /// Verifies that an in-range default maximum series point count passes validation. /// The option value under test. [Theory] [InlineData(2)] // min [InlineData(200)] // default [InlineData(5000)] // max public void Validate_DefaultMaxSeriesPoints_InRange_Passes(int value) { var opts = new KpiHistoryOptions { DefaultMaxSeriesPoints = value }; Assert.True(NewValidator().Validate(null, opts).Succeeded); } /// Verifies that an out-of-range default maximum series point count fails validation. /// The option value under test. [Theory] [InlineData(1)] [InlineData(5001)] [InlineData(0)] public void Validate_DefaultMaxSeriesPoints_OutOfRange_Fails(int value) { var opts = new KpiHistoryOptions { DefaultMaxSeriesPoints = value }; var result = NewValidator().Validate(null, opts); Assert.False(result.Succeeded); Assert.Contains( result.Failures!, f => f.Contains(nameof(KpiHistoryOptions.DefaultMaxSeriesPoints), StringComparison.Ordinal)); } // --------------------------------------------------------------------- // RollupInterval > TimeSpan.Zero // --------------------------------------------------------------------- /// Verifies that a non-positive rollup interval fails validation. /// The rollup interval in hours. [Theory] [InlineData(0)] [InlineData(-1)] public void Validate_RollupInterval_NotPositive_Fails(int hours) { var opts = new KpiHistoryOptions { RollupInterval = TimeSpan.FromHours(hours) }; var result = NewValidator().Validate(null, opts); Assert.False(result.Succeeded); Assert.Contains( result.Failures!, f => f.Contains(nameof(KpiHistoryOptions.RollupInterval), StringComparison.Ordinal)); } // --------------------------------------------------------------------- // RollupLookbackHours in [1, 168] // --------------------------------------------------------------------- /// Verifies that an in-range rollup lookback passes validation. /// The option value under test. [Theory] [InlineData(1)] // min [InlineData(3)] // default [InlineData(168)] // max public void Validate_RollupLookbackHours_InRange_Passes(int value) { var opts = new KpiHistoryOptions { RollupLookbackHours = value }; Assert.True(NewValidator().Validate(null, opts).Succeeded); } /// Verifies that an out-of-range rollup lookback fails validation. /// The option value under test. [Theory] [InlineData(0)] [InlineData(169)] [InlineData(-1)] public void Validate_RollupLookbackHours_OutOfRange_Fails(int value) { var opts = new KpiHistoryOptions { RollupLookbackHours = value }; var result = NewValidator().Validate(null, opts); Assert.False(result.Succeeded); Assert.Contains( result.Failures!, f => f.Contains(nameof(KpiHistoryOptions.RollupLookbackHours), StringComparison.Ordinal)); } // --------------------------------------------------------------------- // RollupRetentionDays in [1, 3650] AND >= RetentionDays // --------------------------------------------------------------------- /// Verifies that an in-range rollup retention period passes validation. /// The option value under test. [Theory] [InlineData(90)] // == RetentionDays default [InlineData(365)] // default [InlineData(3650)] // max public void Validate_RollupRetentionDays_InRange_Passes(int value) { var opts = new KpiHistoryOptions { RollupRetentionDays = value }; Assert.True(NewValidator().Validate(null, opts).Succeeded); } /// Verifies that an out-of-range rollup retention period fails validation. /// The option value under test. [Theory] [InlineData(0)] [InlineData(3651)] [InlineData(-1)] public void Validate_RollupRetentionDays_OutOfRange_Fails(int value) { var opts = new KpiHistoryOptions { RollupRetentionDays = value }; var result = NewValidator().Validate(null, opts); Assert.False(result.Succeeded); Assert.Contains( result.Failures!, f => f.Contains(nameof(KpiHistoryOptions.RollupRetentionDays), StringComparison.Ordinal)); } /// Verifies that a rollup retention shorter than the raw retention fails validation. [Fact] public void Validate_RollupRetentionDays_LessThanRetentionDays_Fails() { // Coherence rule: rollups must outlive raw samples, else long-range trends hit a hole. var opts = new KpiHistoryOptions { RetentionDays = 90, RollupRetentionDays = 30, // in [1,3650] but < RetentionDays }; var result = NewValidator().Validate(null, opts); Assert.False(result.Succeeded); Assert.Contains( result.Failures!, f => f.Contains(nameof(KpiHistoryOptions.RollupRetentionDays), StringComparison.Ordinal) && f.Contains(nameof(KpiHistoryOptions.RetentionDays), StringComparison.Ordinal)); } /// Verifies that a rollup retention equal to the raw retention passes validation. [Fact] public void Validate_RollupRetentionDays_EqualToRetentionDays_Passes() { // Boundary of the coherence rule (>=): equal retention is allowed. var opts = new KpiHistoryOptions { RetentionDays = 120, RollupRetentionDays = 120 }; Assert.True(NewValidator().Validate(null, opts).Succeeded); } // --------------------------------------------------------------------- // RollupThresholdHours >= 24 // --------------------------------------------------------------------- /// Verifies that a rollup threshold at or above the minimum passes validation. /// The option value under test. [Theory] [InlineData(24)] // min [InlineData(168)] // default [InlineData(2160)] // 90 d public void Validate_RollupThresholdHours_AtOrAboveMin_Passes(int value) { var opts = new KpiHistoryOptions { RollupThresholdHours = value }; Assert.True(NewValidator().Validate(null, opts).Succeeded); } /// Verifies that a rollup threshold below the minimum fails validation. /// The option value under test. [Theory] [InlineData(23)] [InlineData(0)] [InlineData(-1)] public void Validate_RollupThresholdHours_BelowMin_Fails(int value) { var opts = new KpiHistoryOptions { RollupThresholdHours = value }; var result = NewValidator().Validate(null, opts); Assert.False(result.Succeeded); Assert.Contains( result.Failures!, f => f.Contains(nameof(KpiHistoryOptions.RollupThresholdHours), StringComparison.Ordinal)); } /// Verifies that violating every rule accumulates every failure message. [Fact] public void Validate_AllRulesViolated_AccumulatesEveryFailure() { // The base class aggregates ALL failures, not just the first — one // message per violated rule. var opts = new KpiHistoryOptions { SampleInterval = TimeSpan.Zero, RetentionDays = 0, PurgeInterval = TimeSpan.Zero, DefaultMaxSeriesPoints = 1, }; var result = NewValidator().Validate(null, opts); Assert.False(result.Succeeded); Assert.Equal(4, result.Failures!.Count()); } }