From cc4ff7029d937dda4c9bb95a74622e403f33a4c0 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 13 Jul 2026 10:10:34 -0400 Subject: [PATCH] chore(saf): eagerly validate SweepBatchLimit/SweepTargetParallelism (plan R2-02 T9) --- .../StoreAndForwardOptionsValidator.cs | 8 +++++ .../StoreAndForwardOptionsValidatorTests.cs | 29 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardOptionsValidator.cs b/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardOptionsValidator.cs index a9ce0f2f..b1a16d33 100644 --- a/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardOptionsValidator.cs +++ b/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardOptionsValidator.cs @@ -31,5 +31,13 @@ public sealed class StoreAndForwardOptionsValidator : OptionsValidatorBase= 0, $"ScadaBridge:StoreAndForward:DefaultMaxRetries must be >= 0 " + $"(was {options.DefaultMaxRetries})."); + + builder.RequireThat(options.SweepBatchLimit >= 0, + $"ScadaBridge:StoreAndForward:SweepBatchLimit must be >= 0 " + + $"(was {options.SweepBatchLimit}); it bounds due rows per retry sweep — 0 means unlimited (legacy)."); + + builder.RequireThat(options.SweepTargetParallelism >= 1, + $"ScadaBridge:StoreAndForward:SweepTargetParallelism must be >= 1 " + + $"(was {options.SweepTargetParallelism}); it caps concurrent (category,target) sweep lanes — 1 means serial."); } } diff --git a/tests/ZB.MOM.WW.ScadaBridge.StoreAndForward.Tests/StoreAndForwardOptionsValidatorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.StoreAndForward.Tests/StoreAndForwardOptionsValidatorTests.cs index 5ffb3063..346516fa 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.StoreAndForward.Tests/StoreAndForwardOptionsValidatorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.StoreAndForward.Tests/StoreAndForwardOptionsValidatorTests.cs @@ -47,4 +47,33 @@ public class StoreAndForwardOptionsValidatorTests Assert.True(result.Failed); Assert.Contains("SqliteDbPath", result.FailureMessage); } + + // ── 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); + } }