namespace ZB.MOM.WW.ScadaBridge.SiteCallAudit.Tests; public class SiteCallAuditOptionsTests { [Fact] public void Defaults_AreExpectedValues() { var options = new SiteCallAuditOptions(); // Stuck threshold mirrors NotificationOutboxOptions.StuckAgeThreshold. Assert.Equal(TimeSpan.FromMinutes(10), options.StuckAgeThreshold); // KPI interval mirrors NotificationOutboxOptions.DeliveredKpiWindow. Assert.Equal(TimeSpan.FromMinutes(1), options.KpiInterval); // Reconciliation tick cadence mirrors SiteAuditReconciliationOptions (#23). Assert.Equal(TimeSpan.FromMinutes(5), options.ReconciliationInterval); // Purge tick cadence mirrors AuditLogPurgeOptions. Assert.Equal(TimeSpan.FromHours(24), options.PurgeInterval); // Retention window mirrors the central audit-store retention policy. Assert.Equal(365, options.RetentionDays); } [Fact] public void ResolvedReconciliationInterval_DefaultsToConfiguredValue() { var options = new SiteCallAuditOptions(); Assert.Equal(options.ReconciliationInterval, options.ResolvedReconciliationInterval); } [Theory] [InlineData(0)] [InlineData(-5)] public void ResolvedReconciliationInterval_ClampsZeroOrNegativeToMinimum(int configuredSeconds) { // A misconfigured 0 / negative interval must never resolve to TimeSpan.Zero // (which would make Akka's ScheduleTellRepeatedlyCancelable spin). The // documented floor is >= 1 second. var options = new SiteCallAuditOptions { ReconciliationInterval = TimeSpan.FromSeconds(configuredSeconds), }; Assert.True( options.ResolvedReconciliationInterval >= TimeSpan.FromSeconds(1), $"expected the resolved interval to clamp to >= 1s, got {options.ResolvedReconciliationInterval}"); Assert.Equal(TimeSpan.FromSeconds(1), options.ResolvedReconciliationInterval); } [Fact] public void ResolvedReconciliationInterval_OverrideBypassesClamp() { // The test-only override drops the cadence below the clamp floor so unit // tests can run the tick at millisecond cadence. var sub1Second = TimeSpan.FromMilliseconds(50); var options = new SiteCallAuditOptions { ReconciliationInterval = TimeSpan.FromMinutes(5), ReconciliationIntervalOverride = sub1Second, }; Assert.Equal(sub1Second, options.ResolvedReconciliationInterval); } [Fact] public void ResolvedPurgeInterval_DefaultsToConfiguredValue() { var options = new SiteCallAuditOptions(); Assert.Equal(options.PurgeInterval, options.ResolvedPurgeInterval); } [Theory] [InlineData(0)] [InlineData(-30)] public void ResolvedPurgeInterval_ClampsZeroOrNegativeToMinimum(int configuredSeconds) { // A misconfigured 0 / negative purge interval clamps to the documented // >= 1 minute floor (the purge is daily, so a more generous floor than // the reconciliation tick). var options = new SiteCallAuditOptions { PurgeInterval = TimeSpan.FromSeconds(configuredSeconds), }; Assert.True( options.ResolvedPurgeInterval >= TimeSpan.FromMinutes(1), $"expected the resolved interval to clamp to >= 1min, got {options.ResolvedPurgeInterval}"); Assert.Equal(TimeSpan.FromMinutes(1), options.ResolvedPurgeInterval); } [Fact] public void ResolvedPurgeInterval_BelowMinuteFloorClampsToMinimum() { // A positive-but-sub-minute config value still clamps to the 1-minute floor. var options = new SiteCallAuditOptions { PurgeInterval = TimeSpan.FromSeconds(5), }; Assert.Equal(TimeSpan.FromMinutes(1), options.ResolvedPurgeInterval); } [Fact] public void ResolvedPurgeInterval_OverrideBypassesClamp() { // The test-only override drops the cadence below the clamp floor so unit // tests can run the purge tick at millisecond cadence. var subMinute = TimeSpan.FromMilliseconds(50); var options = new SiteCallAuditOptions { PurgeInterval = TimeSpan.FromHours(24), PurgeIntervalOverride = subMinute, }; Assert.Equal(subMinute, options.ResolvedPurgeInterval); } }