using Microsoft.Extensions.Options; namespace ZB.MOM.WW.ScadaBridge.StoreAndForward.Tests; /// /// retry intervals feed the background sweep /// timer (zero/negative trips the timer constructor) and the SQLite path backs the /// buffer. These tests assert the /// rejects a bad ScadaBridge:StoreAndForward section with a clear, /// key-naming message rather than crashing later with an opaque exception. /// public class StoreAndForwardOptionsValidatorTests { private static ValidateOptionsResult Validate(StoreAndForwardOptions options) => new StoreAndForwardOptionsValidator().Validate(Options.DefaultName, options); [Fact] public void DefaultOptions_AreValid() { var result = Validate(new StoreAndForwardOptions()); Assert.True(result.Succeeded, result.FailureMessage); } [Fact] public void ZeroDefaultRetryInterval_IsRejected() { var result = Validate(new StoreAndForwardOptions { DefaultRetryInterval = TimeSpan.Zero }); Assert.True(result.Failed); Assert.Contains("DefaultRetryInterval", result.FailureMessage); } [Fact] public void ZeroRetryTimerInterval_IsRejected() { var result = Validate(new StoreAndForwardOptions { RetryTimerInterval = TimeSpan.Zero }); Assert.True(result.Failed); Assert.Contains("RetryTimerInterval", result.FailureMessage); } /// /// The inverse of the rule this replaces. Before LocalDb Phase 2 an empty /// SqliteDbPath was rejected, because it was the live buffer file and an empty /// value failed opaquely at first enqueue. The buffer now lives in the /// consolidated LocalDb database, so the key is only the legacy migration /// source and an empty value legitimately means "nothing to migrate" — a node /// that has already migrated must be able to drop it. /// [Fact] public void EmptySqliteDbPath_IsAccepted_BecauseThePathIsMigrationOnly() { var result = Validate(new StoreAndForwardOptions { SqliteDbPath = "" }); Assert.False(result.Failed); } // ── R2 T9: sweep-tuning eager validation (N4) ── [Theory] [InlineData(-1)] [InlineData(-500)] public void Validate_NegativeSweepBatchLimit_Fails(int limit) { var result = Validate(new StoreAndForwardOptions { SweepBatchLimit = limit }); Assert.True(result.Failed); Assert.Contains("SweepBatchLimit", result.FailureMessage); } [Fact] public void Validate_ZeroSweepBatchLimit_IsValidUnlimitedLegacy() { var result = Validate(new StoreAndForwardOptions { SweepBatchLimit = 0 }); Assert.True(result.Succeeded, result.FailureMessage); } [Theory] [InlineData(0)] [InlineData(-4)] public void Validate_NonPositiveSweepTargetParallelism_Fails(int parallelism) { var result = Validate(new StoreAndForwardOptions { SweepTargetParallelism = parallelism }); Assert.True(result.Failed); Assert.Contains("SweepTargetParallelism", result.FailureMessage); } }